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>
This commit is contained in:
John
2026-06-12 11:50:16 +01:00
committed by GitHub
parent ad9d6a6320
commit 43120bc3c3
11 changed files with 496 additions and 111 deletions

View File

@@ -14,9 +14,12 @@ import {
} from '../base-models';
import {
extractJdkFile,
getNextPageUrlFromLinkHeader,
getDownloadArchiveExtension,
isVersionSatisfies,
renameWinArchive
renameWinArchive,
MAX_PAGINATION_PAGES,
validatePaginationUrl
} from '../../util';
export enum AdoptImplementation {
@@ -125,30 +128,46 @@ export class AdoptDistribution extends JavaBase {
`jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&');
// need to iterate through all pages to retrieve the list of all versions
// Adopt API doesn't provide way to retrieve the count of pages to iterate so infinity loop
let page_index = 0;
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
let availableVersionsUrl: string | null =
`https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
const availableVersions: IAdoptAvailableVersions[] = [];
while (true) {
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
if (core.isDebug() && page_index === 0) {
// url is identical except page_index so print it once for debug
core.debug(
`Gathering available versions from '${availableVersionsUrl}'`
);
}
let pageCount = 0;
if (core.isDebug()) {
core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
}
const paginationPage = (
await this.http.getJson<IAdoptAvailableVersions[]>(availableVersionsUrl)
).result;
while (availableVersionsUrl) {
pageCount++;
const response =
await this.http.getJson<IAdoptAvailableVersions[]>(
availableVersionsUrl
);
const paginationPage = response.result;
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
if (
nextUrl &&
!validatePaginationUrl(nextUrl, 'https://api.adoptopenjdk.net')
) {
core.warning(
`Ignoring pagination link with unexpected origin: ${nextUrl}`
);
availableVersionsUrl = null;
} else {
availableVersionsUrl = nextUrl;
}
if (paginationPage === null || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break;
}
availableVersions.push(...paginationPage);
page_index++;
if (pageCount >= MAX_PAGINATION_PAGES) {
core.warning(
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Adopt releases.`
);
break;
}
}
if (core.isDebug()) {