2021-04-05 13:02:27 +03:00
import os from 'os' ;
import path from 'path' ;
import * as fs from 'fs' ;
import * as semver from 'semver' ;
2022-04-01 00:39:57 +05:30
import * as cache from '@actions/cache' ;
2021-04-05 13:02:27 +03:00
import * as core from '@actions/core' ;
2020-05-02 04:33:15 -07:00
2021-04-05 13:02:27 +03:00
import * as tc from '@actions/tool-cache' ;
2023-03-09 14:49:35 +02:00
import { INPUT_JOB_STATUS , DISTRIBUTIONS_ONLY_MAJOR_VERSION } from './constants' ;
2023-09-20 19:22:11 +08:00
import { OutgoingHttpHeaders } from 'http' ;
2021-08-20 01:19:35 +08:00
2020-05-02 04:33:15 -07:00
export function getTempDir() {
2023-03-09 14:49:35 +02:00
const tempDirectory = process . env [ 'RUNNER_TEMP' ] || os . tmpdir ( ) ;
2021-04-05 13:02:27 +03:00
return tempDirectory ;
}
2023-03-09 14:49:35 +02:00
export function getBooleanInput ( inputName : string , defaultValue = false ) {
return (
( core . getInput ( inputName ) || String ( defaultValue ) ) . toUpperCase ( ) === 'TRUE'
) ;
2021-04-05 13:02:27 +03:00
}
export function getVersionFromToolcachePath ( toolPath : string ) {
if ( toolPath ) {
return path . basename ( path . dirname ( toolPath ) ) ;
}
return toolPath ;
}
export async function extractJdkFile ( toolPath : string , extension? : string ) {
if ( ! extension ) {
2023-03-09 14:49:35 +02:00
extension = toolPath . endsWith ( '.tar.gz' )
? 'tar.gz'
: path . extname ( toolPath ) ;
2021-04-05 13:02:27 +03:00
if ( extension . startsWith ( '.' ) ) {
extension = extension . substring ( 1 ) ;
2020-05-02 04:33:15 -07:00
}
}
2021-04-05 13:02:27 +03:00
switch ( extension ) {
case 'tar.gz' :
case 'tar' :
return await tc . extractTar ( toolPath ) ;
case 'zip' :
return await tc . extractZip ( toolPath ) ;
default :
return await tc . extract7z ( toolPath ) ;
}
}
export function getDownloadArchiveExtension() {
return process . platform === 'win32' ? 'zip' : 'tar.gz' ;
2020-05-02 04:33:15 -07:00
}
2021-04-05 13:02:27 +03:00
export function isVersionSatisfies ( range : string , version : string ) : boolean {
if ( semver . valid ( range ) ) {
2022-03-31 20:49:24 +05:30
// if full version with build digit is provided as a range (such as '1.2.3+4')
2021-04-05 13:02:27 +03:00
// we should check for exact equal via compareBuild
// since semver.satisfies doesn't handle 4th digit
const semRange = semver . parse ( range ) ;
if ( semRange && semRange . build ? . length > 0 ) {
return semver . compareBuild ( range , version ) === 0 ;
}
}
return semver . satisfies ( version , range ) ;
}
2023-03-09 14:49:35 +02:00
export function getToolcachePath (
toolName : string ,
version : string ,
architecture : string
) {
2021-04-05 13:02:27 +03:00
const toolcacheRoot = process . env [ 'RUNNER_TOOL_CACHE' ] ? ? '' ;
const fullPath = path . join ( toolcacheRoot , toolName , version , architecture ) ;
if ( fs . existsSync ( fullPath ) ) {
return fullPath ;
}
return null ;
2020-05-02 04:33:15 -07:00
}
2021-08-20 01:19:35 +08:00
export function isJobStatusSuccess() {
const jobStatus = core . getInput ( INPUT_JOB_STATUS ) ;
return jobStatus === 'success' ;
}
2022-04-01 00:39:57 +05:30
export function isGhes ( ) : boolean {
2023-03-09 14:49:35 +02:00
const ghUrl = new URL (
process . env [ 'GITHUB_SERVER_URL' ] || 'https://github.com'
) ;
2024-10-21 19:57:52 +02:00
const hostname = ghUrl . hostname . trimEnd ( ) . toUpperCase ( ) ;
const isGitHubHost = hostname === 'GITHUB.COM' ;
const isGitHubEnterpriseCloudHost = hostname . endsWith ( '.GHE.COM' ) ;
const isLocalHost = hostname . endsWith ( '.LOCALHOST' ) ;
return ! isGitHubHost && ! isGitHubEnterpriseCloudHost && ! isLocalHost ;
2022-04-01 00:39:57 +05:30
}
export function isCacheFeatureAvailable ( ) : boolean {
2022-12-16 23:04:57 +09:00
if ( cache . isFeatureAvailable ( ) ) {
return true ;
}
2022-04-01 00:39:57 +05:30
2022-12-16 23:04:57 +09:00
if ( isGhes ( ) ) {
core . warning (
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.'
) ;
2022-04-01 00:39:57 +05:30
return false ;
}
2023-03-09 14:49:35 +02:00
core . warning (
'The runner was not able to contact the cache service. Caching will be skipped'
) ;
2022-12-16 23:04:57 +09:00
return false ;
2022-04-01 00:39:57 +05:30
}
2022-12-13 12:45:14 +01:00
export function getVersionFromFileContent (
content : string ,
2024-03-12 19:15:42 +05:30
distributionName : string ,
versionFile : string
2022-12-13 12:45:14 +01:00
) : string | null {
2024-03-12 19:15:42 +05:30
let javaVersionRegExp : RegExp ;
2024-03-14 19:42:58 +05:30
function getFileName ( versionFile : string ) {
return path . basename ( versionFile ) ;
}
const versionFileName = getFileName ( versionFile ) ;
if ( versionFileName == '.tool-versions' ) {
2024-03-12 19:15:42 +05:30
javaVersionRegExp =
2025-09-16 23:53:22 +05:30
/^java\s+(?:\S*-)?(?<version>\d+(?:\.\d+)*([+_.-](?:openj9[-._]?\d[\w.-]*|java\d+|jre[-_\w]*|OpenJDK\d+[\w_.-]*|[a-z0-9]+))*)/im ;
2025-11-25 20:36:29 +01:00
} else if ( versionFileName == '.sdkmanrc' ) {
javaVersionRegExp = /^java\s*=\s*(?<version>[^-]+)/m ;
2024-03-12 19:15:42 +05:30
} else {
2024-03-14 19:42:58 +05:30
javaVersionRegExp = /(?<version>(?<=(^|\s|-))(\d+\S*))(\s|$)/ ;
2024-03-12 19:15:42 +05:30
}
2025-11-25 20:36:29 +01:00
const capturedVersion = content . match ( javaVersionRegExp ) ? . groups ? . version
2022-12-13 12:45:14 +01:00
? ( content . match ( javaVersionRegExp ) ? . groups ? . version as string )
: '' ;
2025-11-25 20:36:29 +01:00
core . debug (
` Parsed version ' ${ capturedVersion } ' from file ' ${ versionFileName } ' `
) ;
if ( ! capturedVersion ) {
2022-12-13 12:45:14 +01:00
return null ;
}
2025-11-25 20:36:29 +01:00
const tentativeVersion = avoidOldNotation ( capturedVersion ) ;
2022-12-13 12:45:14 +01:00
const rawVersion = tentativeVersion . split ( '-' ) [ 0 ] ;
2023-03-09 14:49:35 +02:00
let version = semver . validRange ( rawVersion )
? tentativeVersion
: semver . coerce ( tentativeVersion ) ;
2022-12-13 12:45:14 +01:00
core . debug ( ` Range version from file is ' ${ version } ' ` ) ;
if ( ! version ) {
return null ;
}
if ( DISTRIBUTIONS_ONLY_MAJOR_VERSION . includes ( distributionName ) ) {
const coerceVersion = semver . coerce ( version ) ? ? version ;
version = semver . major ( coerceVersion ) . toString ( ) ;
}
return version . toString ( ) ;
}
// By convention, action expects version 8 in the format `8.*` instead of `1.8`
function avoidOldNotation ( content : string ) : string {
return content . startsWith ( '1.' ) ? content . substring ( 2 ) : content ;
}
2023-04-10 10:29:19 +02:00
export function convertVersionToSemver ( version : number [ ] | string ) {
// Some distributions may use semver-like notation (12.10.2.1, 12.10.2.1.1)
const versionArray = Array . isArray ( version ) ? version : version.split ( '.' ) ;
const mainVersion = versionArray . slice ( 0 , 3 ) . join ( '.' ) ;
if ( versionArray . length > 3 ) {
return ` ${ mainVersion } + ${ versionArray . slice ( 3 ) . join ( '.' ) } ` ;
}
return mainVersion ;
}
2023-09-20 19:22:11 +08:00
export function getGitHubHttpHeaders ( ) : OutgoingHttpHeaders {
2025-11-18 23:34:23 +05:30
const resolvedToken = core . getInput ( 'token' ) || process . env . GITHUB_TOKEN ;
const auth = ! resolvedToken ? undefined : ` token ${ resolvedToken } ` ;
2023-12-01 13:55:03 +00:00
2023-09-20 19:22:11 +08:00
const headers : OutgoingHttpHeaders = {
accept : 'application/vnd.github.VERSION.raw'
} ;
2023-12-01 13:55:03 +00:00
if ( auth ) {
headers . authorization = auth ;
}
2023-09-20 19:22:11 +08:00
return headers ;
}
2024-10-11 03:02:25 +05:30
Implement pagination with link headers for Adoptium based apis (#1014)
* Use Link headers for Adoptium pagination
* Fix nullable pagination URL types and rebuild dist
* Add 1000-page safeguard for JetBrains pagination
* Adjust plan for pagination safeguard scope
* Move pagination safeguard to non-JetBrains installers
* Add 1000-page safeguard to Adopt Temurin and Semeru pagination
* Fix Prettier formatting in adopt, semeru, and temurin installer files
* Fix CI audit failure by updating vulnerable transitive deps
* Address PR review: RFC-compliant Link parsing, SSRF validation, centralized constant
- Make getNextPageUrlFromLinkHeader RFC 8288 compliant by splitting
link-values and checking for rel=next anywhere in the parameters,
not just as the first parameter after the semicolon.
- Add validatePaginationUrl utility to reject pagination URLs that
point to unexpected origins (SSRF mitigation).
- Centralize MAX_PAGINATION_PAGES in util.ts instead of duplicating
across Adopt, Semeru, and Temurin installers.
- Add tests for rel not being the first parameter, and for URL
origin validation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address code review feedback on pagination implementation
- Tighten rel regex with word boundary to prevent false positives
(e.g., rel="nextsomething" no longer matches).
- Use parsed.origin comparison in validatePaginationUrl to correctly
handle explicit default ports (e.g., :443 for HTTPS).
- Fix pagination safeguard tests to use same-origin URLs so they
actually exercise the 1000-page limit instead of being rejected
by origin validation on the first request.
- Add test for rel="nextsomething" not matching.
- Add test for explicit default port acceptance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix prettier formatting in util.test.ts
* Rebuild dist/ to fix check-dist CI failure
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-12 11:50:16 +01:00
export const MAX_PAGINATION_PAGES = 1000 ;
export function getNextPageUrlFromLinkHeader (
headers? : Record < string , string | string [ ] | undefined >
) : string | null {
if ( ! headers ) {
return null ;
}
const linkHeader = headers . link ? ? headers . Link ;
if ( ! linkHeader ) {
return null ;
}
const normalizedLinkHeader = Array . isArray ( linkHeader )
? linkHeader . join ( ',' )
: linkHeader ;
// Split into individual link-values and find the one with rel="next"
// RFC 8288 allows rel to appear anywhere among the parameters
const linkValues = normalizedLinkHeader . split ( /,(?=\s*<)/ ) ;
for ( const linkValue of linkValues ) {
const urlMatch = linkValue . match ( /<([^>]+)>/ ) ;
if ( ! urlMatch ) continue ;
const params = linkValue . slice ( urlMatch [ 0 ] . length ) ;
// Use word boundary to match "next" as a standalone relation type
// RFC 8288 allows space-separated relation types like rel="next prev"
if ( /;\s*rel="?[^"]*\bnext\b/i . test ( params ) ) {
return urlMatch [ 1 ] ;
}
}
return null ;
}
export function validatePaginationUrl (
url : string ,
allowedOrigin : string
) : boolean {
try {
const parsed = new URL ( url ) ;
const allowed = new URL ( allowedOrigin ) ;
return parsed . origin === allowed . origin ;
} catch {
return false ;
}
}
2024-10-11 03:02:25 +05:30
// Rename archive to add extension because after downloading
// archive does not contain extension type and it leads to some issues
// on Windows runners without PowerShell Core.
//
// For default PowerShell Windows it should contain extension type to unpack it.
export function renameWinArchive ( javaArchivePath : string ) : string {
const javaArchivePathRenamed = ` ${ javaArchivePath } .zip ` ;
fs . renameSync ( javaArchivePath , javaArchivePathRenamed ) ;
return javaArchivePathRenamed ;
}