mirror of
https://github.com/github/codeql-action.git
synced 2025-12-24 08:10:06 +08:00
Compare commits
1 Commits
mbg/start-
...
aeisenberg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3f429af7c |
7
lib/analyze.js
generated
7
lib/analyze.js
generated
@@ -119,9 +119,9 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
|
|||||||
logger.info("*************");
|
logger.info("*************");
|
||||||
logger.startGroup(`Downloading custom packs for ${language}`);
|
logger.startGroup(`Downloading custom packs for ${language}`);
|
||||||
const codeql = codeql_1.getCodeQL(config.codeQLCmd);
|
const codeql = codeql_1.getCodeQL(config.codeQLCmd);
|
||||||
const results = await codeql.packDownload(packsWithVersion);
|
const results = await codeql.packDownload(packsWithVersion.map(packWithVersionToString));
|
||||||
logger.info(`Downloaded packs: ${results.packs
|
logger.info(`Downloaded packs: ${results.packs
|
||||||
.map((r) => `${r.name}@${r.version || "latest"}`)
|
.map((r) => `${r.name}@${r.version}`)
|
||||||
.join(", ")}`);
|
.join(", ")}`);
|
||||||
logger.endGroup();
|
logger.endGroup();
|
||||||
}
|
}
|
||||||
@@ -245,4 +245,7 @@ function printLinesOfCodeSummary(logger, language, lineCounts) {
|
|||||||
logger.info(`Counted a baseline of ${lineCounts[language]} lines of code for ${language}.`);
|
logger.info(`Counted a baseline of ${lineCounts[language]} lines of code for ${language}.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function packWithVersionToString(pack) {
|
||||||
|
return pack.version ? `${pack.packName}@${pack.version}` : pack.packName;
|
||||||
|
}
|
||||||
//# sourceMappingURL=analyze.js.map
|
//# sourceMappingURL=analyze.js.map
|
||||||
File diff suppressed because one or more lines are too long
44
lib/codeql.js
generated
44
lib/codeql.js
generated
@@ -273,6 +273,7 @@ function resolveFunction(partialCodeql, methodName, defaultImplementation) {
|
|||||||
*/
|
*/
|
||||||
function setCodeQL(partialCodeql) {
|
function setCodeQL(partialCodeql) {
|
||||||
cachedCodeQL = {
|
cachedCodeQL = {
|
||||||
|
getVersion: resolveFunction(partialCodeql, "getVersion", () => Promise.resolve("0.0.0")),
|
||||||
getPath: resolveFunction(partialCodeql, "getPath", () => "/tmp/dummy-path"),
|
getPath: resolveFunction(partialCodeql, "getPath", () => "/tmp/dummy-path"),
|
||||||
printVersion: resolveFunction(partialCodeql, "printVersion"),
|
printVersion: resolveFunction(partialCodeql, "printVersion"),
|
||||||
getTracerEnv: resolveFunction(partialCodeql, "getTracerEnv"),
|
getTracerEnv: resolveFunction(partialCodeql, "getTracerEnv"),
|
||||||
@@ -306,12 +307,28 @@ function getCachedCodeQL() {
|
|||||||
}
|
}
|
||||||
exports.getCachedCodeQL = getCachedCodeQL;
|
exports.getCachedCodeQL = getCachedCodeQL;
|
||||||
function getCodeQLForCmd(cmd) {
|
function getCodeQLForCmd(cmd) {
|
||||||
|
let version;
|
||||||
return {
|
return {
|
||||||
|
async getVersion() {
|
||||||
|
if (!version) {
|
||||||
|
await this.printVersion();
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
},
|
||||||
getPath() {
|
getPath() {
|
||||||
return cmd;
|
return cmd;
|
||||||
},
|
},
|
||||||
async printVersion() {
|
async printVersion() {
|
||||||
await runTool(cmd, ["version", "--format=json"]);
|
const output = await runTool(cmd, ["version", "--format=json"]);
|
||||||
|
try {
|
||||||
|
version = JSON.parse(output).version;
|
||||||
|
if (!version) {
|
||||||
|
throw new Error("Missing version");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new Error(`Unexpected output from codeql version: ${e}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async getTracerEnv(databasePath) {
|
async getTracerEnv(databasePath) {
|
||||||
// Write tracer-env.js to a temp location.
|
// Write tracer-env.js to a temp location.
|
||||||
@@ -498,22 +515,22 @@ function getCodeQLForCmd(cmd) {
|
|||||||
* downloaded. The check to determine what the latest version is is done
|
* downloaded. The check to determine what the latest version is is done
|
||||||
* each time this package is requested.
|
* each time this package is requested.
|
||||||
*/
|
*/
|
||||||
async packDownload(packs) {
|
async packDownload(packsOrSuites, extraSearchPath) {
|
||||||
const codeqlArgs = [
|
const codeqlArgs = [
|
||||||
"pack",
|
"pack",
|
||||||
"download",
|
"download",
|
||||||
"--format=json",
|
"--format=json",
|
||||||
...getExtraOptionsFromEnv(["pack", "download"]),
|
...getExtraOptionsFromEnv(["pack", "download"]),
|
||||||
...packs.map(packWithVersionToString),
|
...packsOrSuites,
|
||||||
];
|
];
|
||||||
|
if (extraSearchPath) {
|
||||||
|
codeqlArgs.push("--additional-packs", extraSearchPath);
|
||||||
|
}
|
||||||
const output = await runTool(cmd, codeqlArgs);
|
const output = await runTool(cmd, codeqlArgs);
|
||||||
try {
|
try {
|
||||||
const parsedOutput = JSON.parse(output);
|
const parsedOutput = JSON.parse(output);
|
||||||
if (Array.isArray(parsedOutput.packs) &&
|
if (Array.isArray(parsedOutput.packs) &&
|
||||||
// TODO PackDownloadOutput will not include the version if it is not specified
|
parsedOutput.packs.every((p) => p.name && p.version)) {
|
||||||
// in the input. The version is always the latest version available.
|
|
||||||
// It should be added to the output, but this requires a CLI change
|
|
||||||
parsedOutput.packs.every((p) => p.name /* && p.version */)) {
|
|
||||||
return parsedOutput;
|
return parsedOutput;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -544,9 +561,6 @@ function getCodeQLForCmd(cmd) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function packWithVersionToString(pack) {
|
|
||||||
return pack.version ? `${pack.packName}@${pack.version}` : pack.packName;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Gets the options for `path` of `options` as an array of extra option strings.
|
* Gets the options for `path` of `options` as an array of extra option strings.
|
||||||
*/
|
*/
|
||||||
@@ -593,6 +607,16 @@ function getExtraOptions(options, paths, pathInfo) {
|
|||||||
return all.concat(specific);
|
return all.concat(specific);
|
||||||
}
|
}
|
||||||
exports.getExtraOptions = getExtraOptions;
|
exports.getExtraOptions = getExtraOptions;
|
||||||
|
// TODO verify that this is the correct version.
|
||||||
|
const CODEQL_MIN_VERSION_DOWNLOAD_PACKS_FROM_SUITES = ">=2.6.0";
|
||||||
|
/**
|
||||||
|
* Determines if this codeql supports introspecting query suites to look for packages
|
||||||
|
* to download.
|
||||||
|
*/
|
||||||
|
async function supportsDownloadPacksFromSuites(codeQL) {
|
||||||
|
return semver.satisfies(await codeQL.getVersion(), CODEQL_MIN_VERSION_DOWNLOAD_PACKS_FROM_SUITES);
|
||||||
|
}
|
||||||
|
exports.supportsDownloadPacksFromSuites = supportsDownloadPacksFromSuites;
|
||||||
/*
|
/*
|
||||||
* A constant defining the maximum number of characters we will keep from
|
* A constant defining the maximum number of characters we will keep from
|
||||||
* the programs stderr for logging. This serves two purposes:
|
* the programs stderr for logging. This serves two purposes:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
12
lib/codeql.test.js
generated
12
lib/codeql.test.js
generated
@@ -205,4 +205,16 @@ ava_1.default("getCodeQLActionRepository", (t) => {
|
|||||||
const repoEnv = codeql.getCodeQLActionRepository(logger);
|
const repoEnv = codeql.getCodeQLActionRepository(logger);
|
||||||
t.deepEqual(repoEnv, "xxx/yyy");
|
t.deepEqual(repoEnv, "xxx/yyy");
|
||||||
});
|
});
|
||||||
|
ava_1.default("supportsDownloadPacksFromSuites", async (t) => {
|
||||||
|
const mockCodeQL = (mockVersion) => {
|
||||||
|
return {
|
||||||
|
async getVersion() {
|
||||||
|
return mockVersion;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
t.true(await codeql.supportsDownloadPacksFromSuites(mockCodeQL("2.6.0")));
|
||||||
|
t.true(await codeql.supportsDownloadPacksFromSuites(mockCodeQL("2.6.1")));
|
||||||
|
t.false(await codeql.supportsDownloadPacksFromSuites(mockCodeQL("2.5.999")));
|
||||||
|
});
|
||||||
//# sourceMappingURL=codeql.test.js.map
|
//# sourceMappingURL=codeql.test.js.map
|
||||||
File diff suppressed because one or more lines are too long
5
lib/config-utils.js
generated
5
lib/config-utils.js
generated
@@ -12,6 +12,7 @@ const path = __importStar(require("path"));
|
|||||||
const yaml = __importStar(require("js-yaml"));
|
const yaml = __importStar(require("js-yaml"));
|
||||||
const semver = __importStar(require("semver"));
|
const semver = __importStar(require("semver"));
|
||||||
const api = __importStar(require("./api-client"));
|
const api = __importStar(require("./api-client"));
|
||||||
|
const codeql_1 = require("./codeql");
|
||||||
const externalQueries = __importStar(require("./external-queries"));
|
const externalQueries = __importStar(require("./external-queries"));
|
||||||
const languages_1 = require("./languages");
|
const languages_1 = require("./languages");
|
||||||
// Property names from the user-supplied config file.
|
// Property names from the user-supplied config file.
|
||||||
@@ -67,6 +68,10 @@ function validateQueries(resolvedQueries) {
|
|||||||
* queries, and error checking will be suppressed.
|
* queries, and error checking will be suppressed.
|
||||||
*/
|
*/
|
||||||
async function runResolveQueries(codeQL, resultMap, toResolve, extraSearchPath) {
|
async function runResolveQueries(codeQL, resultMap, toResolve, extraSearchPath) {
|
||||||
|
// before running resolve queries, must run download packs, if the CLI is new enough
|
||||||
|
if (await codeql_1.supportsDownloadPacksFromSuites(codeQL)) {
|
||||||
|
await codeQL.packDownload(toResolve, extraSearchPath);
|
||||||
|
}
|
||||||
const resolvedQueries = await codeQL.resolveQueries(toResolve, extraSearchPath);
|
const resolvedQueries = await codeQL.resolveQueries(toResolve, extraSearchPath);
|
||||||
if (extraSearchPath !== undefined) {
|
if (extraSearchPath !== undefined) {
|
||||||
validateQueries(resolvedQueries);
|
validateQueries(resolvedQueries);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -229,10 +229,12 @@ export async function runQueries(
|
|||||||
logger.startGroup(`Downloading custom packs for ${language}`);
|
logger.startGroup(`Downloading custom packs for ${language}`);
|
||||||
|
|
||||||
const codeql = getCodeQL(config.codeQLCmd);
|
const codeql = getCodeQL(config.codeQLCmd);
|
||||||
const results = await codeql.packDownload(packsWithVersion);
|
const results = await codeql.packDownload(
|
||||||
|
packsWithVersion.map(packWithVersionToString)
|
||||||
|
);
|
||||||
logger.info(
|
logger.info(
|
||||||
`Downloaded packs: ${results.packs
|
`Downloaded packs: ${results.packs
|
||||||
.map((r) => `${r.name}@${r.version || "latest"}`)
|
.map((r) => `${r.name}@${r.version}`)
|
||||||
.join(", ")}`
|
.join(", ")}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -445,3 +447,7 @@ function printLinesOfCodeSummary(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function packWithVersionToString(pack: configUtils.PackWithVersion): string {
|
||||||
|
return pack.version ? `${pack.packName}@${pack.version}` : pack.packName;
|
||||||
|
}
|
||||||
|
|||||||
@@ -390,3 +390,17 @@ test("getCodeQLActionRepository", (t) => {
|
|||||||
const repoEnv = codeql.getCodeQLActionRepository(logger);
|
const repoEnv = codeql.getCodeQLActionRepository(logger);
|
||||||
t.deepEqual(repoEnv, "xxx/yyy");
|
t.deepEqual(repoEnv, "xxx/yyy");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("supportsDownloadPacksFromSuites", async (t) => {
|
||||||
|
const mockCodeQL = (mockVersion: string) => {
|
||||||
|
return {
|
||||||
|
async getVersion() {
|
||||||
|
return mockVersion;
|
||||||
|
},
|
||||||
|
} as codeql.CodeQL;
|
||||||
|
};
|
||||||
|
|
||||||
|
t.true(await codeql.supportsDownloadPacksFromSuites(mockCodeQL("2.6.0")));
|
||||||
|
t.true(await codeql.supportsDownloadPacksFromSuites(mockCodeQL("2.6.1")));
|
||||||
|
t.false(await codeql.supportsDownloadPacksFromSuites(mockCodeQL("2.5.999")));
|
||||||
|
});
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import * as semver from "semver";
|
|||||||
|
|
||||||
import { isRunningLocalAction, getRelativeScriptPath } from "./actions-util";
|
import { isRunningLocalAction, getRelativeScriptPath } from "./actions-util";
|
||||||
import * as api from "./api-client";
|
import * as api from "./api-client";
|
||||||
import { PackWithVersion } from "./config-utils";
|
|
||||||
import * as defaults from "./defaults.json"; // Referenced from codeql-action-sync-tool!
|
import * as defaults from "./defaults.json"; // Referenced from codeql-action-sync-tool!
|
||||||
import { errorMatchers } from "./error-matcher";
|
import { errorMatchers } from "./error-matcher";
|
||||||
import { Language } from "./languages";
|
import { Language } from "./languages";
|
||||||
@@ -50,6 +49,11 @@ export class CommandInvocationError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CodeQL {
|
export interface CodeQL {
|
||||||
|
/**
|
||||||
|
* Gets the version of the CodeQL CLI.
|
||||||
|
*/
|
||||||
|
getVersion(): Promise<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the path of the CodeQL executable.
|
* Get the path of the CodeQL executable.
|
||||||
*/
|
*/
|
||||||
@@ -99,7 +103,10 @@ export interface CodeQL {
|
|||||||
/**
|
/**
|
||||||
* Run 'codeql pack download'.
|
* Run 'codeql pack download'.
|
||||||
*/
|
*/
|
||||||
packDownload(packs: PackWithVersion[]): Promise<PackDownloadOutput>;
|
packDownload(
|
||||||
|
packsOrSuites: string[],
|
||||||
|
extraSearchPath?: string
|
||||||
|
): Promise<PackDownloadOutput>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run 'codeql database cleanup'.
|
* Run 'codeql database cleanup'.
|
||||||
@@ -489,6 +496,9 @@ function resolveFunction<T>(
|
|||||||
*/
|
*/
|
||||||
export function setCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
|
export function setCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
|
||||||
cachedCodeQL = {
|
cachedCodeQL = {
|
||||||
|
getVersion: resolveFunction(partialCodeql, "getVersion", () =>
|
||||||
|
Promise.resolve("0.0.0")
|
||||||
|
),
|
||||||
getPath: resolveFunction(partialCodeql, "getPath", () => "/tmp/dummy-path"),
|
getPath: resolveFunction(partialCodeql, "getPath", () => "/tmp/dummy-path"),
|
||||||
printVersion: resolveFunction(partialCodeql, "printVersion"),
|
printVersion: resolveFunction(partialCodeql, "printVersion"),
|
||||||
getTracerEnv: resolveFunction(partialCodeql, "getTracerEnv"),
|
getTracerEnv: resolveFunction(partialCodeql, "getTracerEnv"),
|
||||||
@@ -528,12 +538,27 @@ export function getCachedCodeQL(): CodeQL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCodeQLForCmd(cmd: string): CodeQL {
|
function getCodeQLForCmd(cmd: string): CodeQL {
|
||||||
|
let version: string;
|
||||||
return {
|
return {
|
||||||
|
async getVersion() {
|
||||||
|
if (!version) {
|
||||||
|
await this.printVersion();
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
},
|
||||||
getPath() {
|
getPath() {
|
||||||
return cmd;
|
return cmd;
|
||||||
},
|
},
|
||||||
async printVersion() {
|
async printVersion() {
|
||||||
await runTool(cmd, ["version", "--format=json"]);
|
const output = await runTool(cmd, ["version", "--format=json"]);
|
||||||
|
try {
|
||||||
|
version = JSON.parse(output).version;
|
||||||
|
if (!version) {
|
||||||
|
throw new Error("Missing version");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Unexpected output from codeql version: ${e}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async getTracerEnv(databasePath: string) {
|
async getTracerEnv(databasePath: string) {
|
||||||
// Write tracer-env.js to a temp location.
|
// Write tracer-env.js to a temp location.
|
||||||
@@ -779,25 +804,29 @@ function getCodeQLForCmd(cmd: string): CodeQL {
|
|||||||
* downloaded. The check to determine what the latest version is is done
|
* downloaded. The check to determine what the latest version is is done
|
||||||
* each time this package is requested.
|
* each time this package is requested.
|
||||||
*/
|
*/
|
||||||
async packDownload(packs: PackWithVersion[]): Promise<PackDownloadOutput> {
|
async packDownload(
|
||||||
|
packsOrSuites: string[],
|
||||||
|
extraSearchPath?: string
|
||||||
|
): Promise<PackDownloadOutput> {
|
||||||
const codeqlArgs = [
|
const codeqlArgs = [
|
||||||
"pack",
|
"pack",
|
||||||
"download",
|
"download",
|
||||||
"--format=json",
|
"--format=json",
|
||||||
...getExtraOptionsFromEnv(["pack", "download"]),
|
...getExtraOptionsFromEnv(["pack", "download"]),
|
||||||
...packs.map(packWithVersionToString),
|
...packsOrSuites,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (extraSearchPath) {
|
||||||
|
codeqlArgs.push("--additional-packs", extraSearchPath);
|
||||||
|
}
|
||||||
|
|
||||||
const output = await runTool(cmd, codeqlArgs);
|
const output = await runTool(cmd, codeqlArgs);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedOutput: PackDownloadOutput = JSON.parse(output);
|
const parsedOutput: PackDownloadOutput = JSON.parse(output);
|
||||||
if (
|
if (
|
||||||
Array.isArray(parsedOutput.packs) &&
|
Array.isArray(parsedOutput.packs) &&
|
||||||
// TODO PackDownloadOutput will not include the version if it is not specified
|
parsedOutput.packs.every((p) => p.name && p.version)
|
||||||
// in the input. The version is always the latest version available.
|
|
||||||
// It should be added to the output, but this requires a CLI change
|
|
||||||
parsedOutput.packs.every((p) => p.name /* && p.version */)
|
|
||||||
) {
|
) {
|
||||||
return parsedOutput;
|
return parsedOutput;
|
||||||
} else {
|
} else {
|
||||||
@@ -835,10 +864,6 @@ function getCodeQLForCmd(cmd: string): CodeQL {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function packWithVersionToString(pack: PackWithVersion): string {
|
|
||||||
return pack.version ? `${pack.packName}@${pack.version}` : pack.packName;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Gets the options for `path` of `options` as an array of extra option strings.
|
* Gets the options for `path` of `options` as an array of extra option strings.
|
||||||
*/
|
*/
|
||||||
@@ -899,6 +924,22 @@ export function getExtraOptions(
|
|||||||
return all.concat(specific);
|
return all.concat(specific);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO verify that this is the correct version.
|
||||||
|
const CODEQL_MIN_VERSION_DOWNLOAD_PACKS_FROM_SUITES = ">=2.6.0";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this codeql supports introspecting query suites to look for packages
|
||||||
|
* to download.
|
||||||
|
*/
|
||||||
|
export async function supportsDownloadPacksFromSuites(
|
||||||
|
codeQL: CodeQL
|
||||||
|
): Promise<boolean> {
|
||||||
|
return semver.satisfies(
|
||||||
|
await codeQL.getVersion(),
|
||||||
|
CODEQL_MIN_VERSION_DOWNLOAD_PACKS_FROM_SUITES
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A constant defining the maximum number of characters we will keep from
|
* A constant defining the maximum number of characters we will keep from
|
||||||
* the programs stderr for logging. This serves two purposes:
|
* the programs stderr for logging. This serves two purposes:
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import * as yaml from "js-yaml";
|
|||||||
import * as semver from "semver";
|
import * as semver from "semver";
|
||||||
|
|
||||||
import * as api from "./api-client";
|
import * as api from "./api-client";
|
||||||
import { CodeQL, ResolveQueriesOutput } from "./codeql";
|
import {
|
||||||
|
CodeQL,
|
||||||
|
ResolveQueriesOutput,
|
||||||
|
supportsDownloadPacksFromSuites,
|
||||||
|
} from "./codeql";
|
||||||
import * as externalQueries from "./external-queries";
|
import * as externalQueries from "./external-queries";
|
||||||
import { Language, parseLanguage } from "./languages";
|
import { Language, parseLanguage } from "./languages";
|
||||||
import { Logger } from "./logging";
|
import { Logger } from "./logging";
|
||||||
@@ -202,6 +206,10 @@ async function runResolveQueries(
|
|||||||
toResolve: string[],
|
toResolve: string[],
|
||||||
extraSearchPath: string | undefined
|
extraSearchPath: string | undefined
|
||||||
) {
|
) {
|
||||||
|
// before running resolve queries, must run download packs, if the CLI is new enough
|
||||||
|
if (await supportsDownloadPacksFromSuites(codeQL)) {
|
||||||
|
await codeQL.packDownload(toResolve, extraSearchPath);
|
||||||
|
}
|
||||||
const resolvedQueries = await codeQL.resolveQueries(
|
const resolvedQueries = await codeQL.resolveQueries(
|
||||||
toResolve,
|
toResolve,
|
||||||
extraSearchPath
|
extraSearchPath
|
||||||
|
|||||||
Reference in New Issue
Block a user