"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DOCKER_HUB = exports.gitRefLabel = exports.sourceLabels = exports.sourceLabel = exports.imageUrlLabel = exports.dockerDatasourceId = void 0;
exports.isDockerHost = isDockerHost;
exports.getAuthHeaders = getAuthHeaders;
exports.getRegistryRepository = getRegistryRepository;
exports.extractDigestFromResponseBody = extractDigestFromResponseBody;
exports.findLatestStable = findLatestStable;
exports.findHelmSourceUrl = findHelmSourceUrl;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const auth_header_1 = require("auth-header");
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const external_host_error_1 = require("../../../types/errors/external-host-error");
const array_1 = require("../../../util/array");
const common_1 = require("../../../util/common");
const url_1 = require("../../../util/git/url");
const hash_1 = require("../../../util/hash");
const hostRules = tslib_1.__importStar(require("../../../util/host-rules"));
const regex_1 = require("../../../util/regex");
const sanitize_1 = require("../../../util/sanitize");
const url_2 = require("../../../util/url");
const docker_1 = require("../../versioning/docker");
const util_1 = require("../util");
const ecr_1 = require("./ecr");
const google_1 = require("./google");
exports.dockerDatasourceId = 'docker';
exports.imageUrlLabel = 'org.opencontainers.image.url';
exports.sourceLabel = 'org.opencontainers.image.source';
exports.sourceLabels = [exports.sourceLabel, 'org.label-schema.vcs-url'];
exports.gitRefLabel = 'org.opencontainers.image.revision';
exports.DOCKER_HUB = 'https://index.docker.io';
function isDockerHost(host) {
    const regex = (0, regex_1.regEx)(/(?:^|\.)docker\.io$/);
    return regex.test(host);
}
async function getAuthHeaders(http, registryHost, dockerRepository, apiCheckUrl = `${registryHost}/v2/`) {
    try {
        const options = {
            throwHttpErrors: false,
            noAuth: true,
        };
        const apiCheckResponse = apiCheckUrl.endsWith('/v2/')
            ? await http.get(apiCheckUrl, options)
            : // use json request, as this will be cached for tags, so it returns json
                // TODO: add cache test
                await http.getJson(apiCheckUrl, options);
        if (apiCheckResponse.statusCode === 200) {
            logger_1.logger.debug(`No registry auth required for ${apiCheckUrl}`);
            return {};
        }
        if (apiCheckResponse.statusCode === 404) {
            logger_1.logger.debug(`Page Not Found ${apiCheckUrl}`);
            // throw error up to be caught and potentially retried with library/ prefix
            throw new Error(error_messages_1.PAGE_NOT_FOUND_ERROR);
        }
        if (apiCheckResponse.statusCode !== 401 ||
            !is_1.default.nonEmptyString(apiCheckResponse.headers['www-authenticate'])) {
            logger_1.logger.warn({ apiCheckUrl, res: apiCheckResponse }, 'Invalid registry response');
            return null;
        }
        const authenticateHeader = (0, auth_header_1.parse)(apiCheckResponse.headers['www-authenticate']);
        const opts = hostRules.find({
            hostType: exports.dockerDatasourceId,
            url: apiCheckUrl,
        });
        if (ecr_1.ecrRegex.test(registryHost)) {
            logger_1.logger.once.debug(`hostRules: ecr auth for ${registryHost}`);
            logger_1.logger.trace({ registryHost, dockerRepository }, `Using ecr auth for Docker registry`);
            const [, region] = (0, array_1.coerceArray)(ecr_1.ecrRegex.exec(registryHost));
            const auth = await (0, ecr_1.getECRAuthToken)(region, opts);
            if (auth) {
                opts.headers = { authorization: `Basic ${auth}` };
            }
        }
        else if (google_1.googleRegex.test(registryHost) &&
            typeof opts.username === 'undefined' &&
            typeof opts.password === 'undefined' &&
            typeof opts.token === 'undefined') {
            logger_1.logger.once.debug(`hostRules: google auth for ${registryHost}`);
            logger_1.logger.trace({ registryHost, dockerRepository }, `Using google auth for Docker registry`);
            const auth = await (0, util_1.getGoogleAuthToken)();
            if (auth) {
                opts.headers = { authorization: `Basic ${auth}` };
            }
            else {
                logger_1.logger.once.debug({ registryHost, dockerRepository }, 'Could not get Google access token, using no auth');
            }
        }
        else if (opts.username && opts.password) {
            logger_1.logger.once.debug(`hostRules: basic auth for ${registryHost}`);
            logger_1.logger.trace({ registryHost, dockerRepository }, `Using basic auth for Docker registry`);
            const auth = Buffer.from(`${opts.username}:${opts.password}`).toString('base64');
            opts.headers = { authorization: `Basic ${auth}` };
        }
        else if (opts.token) {
            const authType = opts.authType ?? 'Bearer';
            logger_1.logger.once.debug(`hostRules: ${authType} token auth for ${registryHost}`);
            logger_1.logger.trace({ registryHost, dockerRepository }, `Using ${authType} token for Docker registry`);
            opts.headers = { authorization: `${authType} ${opts.token}` };
        }
        delete opts.username;
        delete opts.password;
        delete opts.token;
        // If realm isn't an url, we should directly use auth header
        // Can happen when we get a Basic auth or some other auth type
        // * WWW-Authenticate: Basic realm="Artifactory Realm"
        // * Www-Authenticate: Basic realm="https://123456789.dkr.ecr.eu-central-1.amazonaws.com/",service="ecr.amazonaws.com"
        // * www-authenticate: Bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull"
        // * www-authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io"
        if (authenticateHeader.scheme.toUpperCase() !== 'BEARER' ||
            !is_1.default.string(authenticateHeader.params.realm) ||
            (0, url_2.parseUrl)(authenticateHeader.params.realm) === null) {
            logger_1.logger.once.debug(`hostRules: testing direct auth for ${registryHost}`);
            logger_1.logger.trace({ registryHost, dockerRepository, authenticateHeader }, `Invalid realm, testing direct auth`);
            return opts.headers ?? null;
        }
        const authUrl = new URL(`${authenticateHeader.params.realm}`);
        // repo isn't known to server yet, so causing wrong scope `repository:user/image:pull`
        if (is_1.default.string(authenticateHeader.params.scope) &&
            !apiCheckUrl.endsWith('/v2/')) {
            authUrl.searchParams.append('scope', authenticateHeader.params.scope);
        }
        else {
            authUrl.searchParams.append('scope', `repository:${dockerRepository}:pull`);
        }
        if (is_1.default.string(authenticateHeader.params.service)) {
            authUrl.searchParams.append('service', authenticateHeader.params.service);
        }
        logger_1.logger.trace({ registryHost, dockerRepository, authUrl: authUrl.href }, `Obtaining docker registry token`);
        opts.noAuth = true;
        const authResponse = (await http.getJson(authUrl.href, opts)).body;
        const token = authResponse.token ?? authResponse.access_token;
        // istanbul ignore if
        if (!token) {
            logger_1.logger.warn('Failed to obtain docker registry token');
            return null;
        }
        // sanitize token
        (0, sanitize_1.addSecretForSanitizing)(token);
        return {
            authorization: `Bearer ${token}`,
        };
    }
    catch (err) /* istanbul ignore next */ {
        if (err.host === 'quay.io') {
            // TODO: debug why quay throws errors (#9604)
            return null;
        }
        if (err.statusCode === 401) {
            logger_1.logger.debug({ registryHost, dockerRepository }, 'Unauthorized docker lookup');
            logger_1.logger.debug({ err });
            return null;
        }
        if (err.statusCode === 403) {
            logger_1.logger.debug({ registryHost, dockerRepository }, 'Not allowed to access docker registry');
            logger_1.logger.debug({ err });
            return null;
        }
        if (err.name === 'RequestError' && isDockerHost(registryHost)) {
            throw new external_host_error_1.ExternalHostError(err);
        }
        if (err.statusCode === 429 && isDockerHost(registryHost)) {
            throw new external_host_error_1.ExternalHostError(err);
        }
        if (err.statusCode >= 500 && err.statusCode < 600) {
            throw new external_host_error_1.ExternalHostError(err);
        }
        if (err.message === error_messages_1.PAGE_NOT_FOUND_ERROR) {
            throw err;
        }
        if (err.message === error_messages_1.HOST_DISABLED) {
            logger_1.logger.trace({ registryHost, dockerRepository, err }, 'Host disabled');
            return null;
        }
        logger_1.logger.warn({ registryHost, dockerRepository, err }, 'Error obtaining docker token');
        return null;
    }
}
function getRegistryRepository(packageName, registryUrl) {
    if (registryUrl !== exports.DOCKER_HUB) {
        const registryEndingWithSlash = (0, url_2.ensureTrailingSlash)(registryUrl.replace((0, regex_1.regEx)(/^https?:\/\//), ''));
        if (packageName.startsWith(registryEndingWithSlash)) {
            let registryHost = (0, url_2.trimTrailingSlash)(registryUrl);
            if (!(0, regex_1.regEx)(/^https?:\/\//).test(registryHost)) {
                registryHost = `https://${registryHost}`;
            }
            let dockerRepository = packageName.replace(registryEndingWithSlash, '');
            const fullUrl = `${registryHost}/${dockerRepository}`;
            const { origin, pathname } = (0, url_2.parseUrl)(fullUrl);
            registryHost = origin;
            dockerRepository = pathname.substring(1);
            return {
                registryHost,
                dockerRepository,
            };
        }
    }
    let registryHost = registryUrl;
    const split = packageName.split('/');
    if (split.length > 1 && (split[0].includes('.') || split[0].includes(':'))) {
        [registryHost] = split;
        split.shift();
    }
    let dockerRepository = split.join('/');
    if (!(0, regex_1.regEx)(/^https?:\/\//).test(registryHost)) {
        registryHost = `https://${registryHost}`;
    }
    const { path, base } = (0, regex_1.regEx)(/^(?<base>https:\/\/[^/]+)\/(?<path>.+)$/).exec(registryHost)
        ?.groups ?? {};
    if (base && path) {
        registryHost = base;
        dockerRepository = `${(0, url_2.trimTrailingSlash)(path)}/${dockerRepository}`;
    }
    registryHost = registryHost
        .replace('https://docker.io', 'https://index.docker.io')
        .replace('https://registry-1.docker.io', 'https://index.docker.io');
    const opts = hostRules.find({
        hostType: exports.dockerDatasourceId,
        url: registryHost,
    });
    if (opts?.insecureRegistry) {
        registryHost = registryHost.replace('https', 'http');
    }
    if (registryHost.endsWith('.docker.io') && !dockerRepository.includes('/')) {
        dockerRepository = 'library/' + dockerRepository;
    }
    return {
        registryHost,
        dockerRepository,
    };
}
function extractDigestFromResponseBody(manifestResponse) {
    return 'sha256:' + (0, hash_1.toSha256)(manifestResponse.body);
}
function findLatestStable(tags) {
    let stable = null;
    for (const tag of tags) {
        if (!docker_1.api.isValid(tag) || !docker_1.api.isStable(tag)) {
            continue;
        }
        if (!stable || docker_1.api.isGreaterThan(tag, stable)) {
            stable = tag;
        }
    }
    return stable;
}
const chartRepo = (0, regex_1.regEx)(/charts?|helm|helm-charts/i);
function isPossibleChartRepo(url) {
    if ((0, common_1.detectPlatform)(url) === null) {
        return false;
    }
    const parsed = (0, url_1.parseGitUrl)(url);
    return chartRepo.test(parsed.name);
}
function findHelmSourceUrl(release) {
    if (release.home && isPossibleChartRepo(release.home)) {
        return release.home;
    }
    if (!release.sources?.length) {
        return null;
    }
    for (const url of release.sources) {
        if (isPossibleChartRepo(url)) {
            return url;
        }
    }
    // fallback
    return release.sources[0];
}
//# sourceMappingURL=common.js.map