mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 09:14:58 +00:00
Parse GitHub release URLs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
BundleSelectionOptions,
|
||||
getPublicRelease,
|
||||
getRelease,
|
||||
parseCodeQLReleaseUrl,
|
||||
selectBundle,
|
||||
} from "./codeql-release";
|
||||
import { ActionsEnvVars } from "./environment";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
getTestEnv,
|
||||
initAllState,
|
||||
LoggedMessage,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
} from "./testing-utils";
|
||||
import { ConfigurationError, GitHubVariant } from "./util";
|
||||
|
||||
@@ -198,3 +200,93 @@ test("getPublicRelease constructs download URLs without looking up the release",
|
||||
);
|
||||
t.deepEqual(fixture.requests, []);
|
||||
});
|
||||
|
||||
test("parseCodeQLReleaseUrl accepts web and legacy links and decodes tags once", (t) => {
|
||||
for (const [suffix, tagName] of [
|
||||
[`tag/${TAG}`, TAG],
|
||||
[TAG, TAG],
|
||||
["codeql-bundle-20230120", "codeql-bundle-20230120"],
|
||||
["codeql-bundle-v2.27.1-rc.1", "codeql-bundle-v2.27.1-rc.1"],
|
||||
["tag/run-123", "run-123"],
|
||||
["tag/build/123", "build/123"],
|
||||
["tag/build%2F123%2Brc%231", "build/123+rc#1"],
|
||||
["tag/build%252F123", "build%2F123"],
|
||||
[`tag/${TAG}/?expanded=true#assets`, TAG],
|
||||
]) {
|
||||
t.deepEqual(
|
||||
parseCodeQLReleaseUrl(
|
||||
`https://github.com/octo/tools/releases/${suffix}`,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
),
|
||||
{ ...REFERENCE, isCurrentInstance: true, tagName },
|
||||
);
|
||||
}
|
||||
t.throws(
|
||||
() =>
|
||||
parseCodeQLReleaseUrl(
|
||||
"https://github.com/octo/tools/releases/tag/%zz",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
),
|
||||
{ instanceOf: ConfigurationError, message: /Invalid URL encoding/ },
|
||||
);
|
||||
});
|
||||
|
||||
test("parseCodeQLReleaseUrl matches the current instance and GitHub.com by origin", (t) => {
|
||||
for (const [url, origin, serverURL] of [
|
||||
["https://github.example.test", "https://github.com", "https://github.com"],
|
||||
[
|
||||
"https://github.example.test/",
|
||||
"https://github.example.test",
|
||||
"https://github.example.test",
|
||||
],
|
||||
[
|
||||
"https://GitHub.Example.test",
|
||||
"https://github.example.test",
|
||||
"https://github.example.test",
|
||||
],
|
||||
[
|
||||
"https://github.example.test:443",
|
||||
"https://GITHUB.example.test:443",
|
||||
"https://github.example.test",
|
||||
],
|
||||
]) {
|
||||
t.deepEqual(
|
||||
parseCodeQLReleaseUrl(`${origin}/octo/tools/releases/tag/${TAG}`, {
|
||||
auth: "token",
|
||||
url,
|
||||
apiURL: undefined,
|
||||
}),
|
||||
{
|
||||
...REFERENCE,
|
||||
serverURL,
|
||||
isCurrentInstance: serverURL !== "https://github.com",
|
||||
},
|
||||
`${url} ${origin}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("parseCodeQLReleaseUrl excludes archives, REST references and untrusted URLs", (t) => {
|
||||
for (const input of [
|
||||
"/tmp/codeql-bundle.tar.zst",
|
||||
"nightly",
|
||||
`https://github.com/octo/tools/releases/download/${TAG}/${JAVA}`,
|
||||
"https://api.github.com/repos/octo/tools/releases/assets/123",
|
||||
"https://api.github.com/repos/octo/tools/releases/123",
|
||||
`https://api.github.com/repos/octo/tools/releases/tags/${TAG}`,
|
||||
"https://github.com/octo/tools/releases/latest",
|
||||
"https://github.com/octo/tools/releases/tag/",
|
||||
"https://github.com/octo/tools/releases/run-123",
|
||||
`https://github.com.example.test/octo/tools/releases/tag/${TAG}`,
|
||||
`https://github.com:8443/octo/tools/releases/tag/${TAG}`,
|
||||
`https://github.com@example.test/octo/tools/releases/tag/${TAG}`,
|
||||
`https://user@github.com/octo/tools/releases/tag/${TAG}`,
|
||||
`http://github.com/octo/tools/releases/tag/${TAG}`,
|
||||
]) {
|
||||
t.is(
|
||||
parseCodeQLReleaseUrl(input, SAMPLE_DOTCOM_API_DETAILS),
|
||||
undefined,
|
||||
input,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as semver from "semver";
|
||||
|
||||
import { ActionState } from "./action-common";
|
||||
import type { GitHubApiDetails } from "./api-client";
|
||||
import { CodeQLBundle, getCodeQLBundleName } from "./codeql-bundle";
|
||||
import { CODEQL_VERSION_ZSTD_BUNDLE } from "./feature-flags";
|
||||
import {
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
} from "./per-language-bundles";
|
||||
import { BundlePlatform } from "./platform";
|
||||
import type { CompressionMethod } from "./tar";
|
||||
import { ConfigurationError, GitHubVariant } from "./util";
|
||||
import { ConfigurationError, GITHUB_DOTCOM_URL, GitHubVariant } from "./util";
|
||||
|
||||
/** Identifies a release on a GitHub instance. */
|
||||
export interface CodeQLReleaseReference {
|
||||
@@ -27,6 +28,64 @@ export interface CodeQLRelease {
|
||||
getAssetURL(name: string): string | undefined;
|
||||
}
|
||||
|
||||
/** A release requested by URL, on this GitHub instance or on GitHub.com. */
|
||||
export interface RequestedRelease extends CodeQLReleaseReference {
|
||||
isCurrentInstance: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recognizes release pages, including legacy bundle links, but never asset URLs.
|
||||
*
|
||||
* We only accept https URLs without credentials, on this GitHub instance, whose API we can use, or
|
||||
* on GitHub.com, whose public releases we can download without credentials. The tag is
|
||||
* percent-decoded once, and may contain `/`.
|
||||
*/
|
||||
export function parseCodeQLReleaseUrl(
|
||||
input: string,
|
||||
apiDetails: GitHubApiDetails,
|
||||
): RequestedRelease | undefined {
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(input);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
if (url.protocol !== "https:" || url.username || url.password) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// GitHub instances are served from the root of their origin.
|
||||
const isCurrentInstance = url.origin === new URL(apiDetails.url).origin;
|
||||
if (!isCurrentInstance && url.origin !== new URL(GITHUB_DOTCOM_URL).origin) {
|
||||
return undefined;
|
||||
}
|
||||
// Older links to bundle releases omit "tag/", which GitHub still supports. We only accept this
|
||||
// form for bundle tags, since other names can clash with routes like `releases/latest`.
|
||||
const match = url.pathname
|
||||
.replace(/\/$/, "")
|
||||
.match(
|
||||
/^\/([\w.-]+)\/([\w.-]+)\/releases\/(?:tag\/(.+)|(codeql-bundle-[^/]+))$/,
|
||||
);
|
||||
if (match === null) {
|
||||
return undefined;
|
||||
}
|
||||
let tagName: string;
|
||||
try {
|
||||
tagName = decodeURIComponent(match[3] ?? match[4]);
|
||||
} catch {
|
||||
throw new ConfigurationError(
|
||||
"Invalid URL encoding in the CodeQL release tag.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
serverURL: url.origin,
|
||||
isCurrentInstance,
|
||||
owner: match[1],
|
||||
repo: match[2],
|
||||
tagName,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a tag for use in a URL path. Slashes stay as path separators, as in GitHub's release URLs
|
||||
* for tags like `build/123`.
|
||||
|
||||
Reference in New Issue
Block a user