OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / @peculiar / utils / build / esm / pem / pem.js

130 lines · javascript ref:
import { base64 } from "../encoding/base64.js";
const LABEL_REGEX = /^[A-Z0-9][A-Z0-9 ._-]*[A-Z0-9]$/i;
const PEM_BLOCK_REGEX = /-----BEGIN ([^-]+)-----([\s\S]*?)-----END \1-----/g;
function assertLabel(label) {
    if (!LABEL_REGEX.test(label)) {
        throw new TypeError(`Invalid PEM label '${label}'`);
    }
}
function wrap(text, lineLength) {
    const result = [];
    for (let i = 0; i < text.length; i += lineLength) {
        result.push(text.slice(i, i + lineLength));
    }
    return result;
}
function parseBody(body) {
    const normalized = body.trim().replace(/\r\n/g, "\n");
    const lines = normalized.split("\n").map((line) => line.trim()).filter(Boolean);
    const headers = {};
    let index = 0;
    for (; index < lines.length; index++) {
        const line = lines[index];
        const separator = line.indexOf(":");
        if (separator <= 0) {
            break;
        }
        headers[line.slice(0, separator).trim()] = line.slice(separator + 1).trim();
    }
    return {
        headers: Object.keys(headers).length ? headers : undefined,
        base64Lines: lines.slice(index),
        base64Text: lines.slice(index).join(""),
    };
}
function detectNewline(text) {
    return /\r\n/.test(text) ? "\r\n" : "\n";
}
function collectBlocks(text, options = {}) {
    const blocks = [];
    const requestedLabel = options.label;
    let match;
    PEM_BLOCK_REGEX.lastIndex = 0;
    while ((match = PEM_BLOCK_REGEX.exec(text))) {
        const label = match[1].trim();
        if (requestedLabel && label !== requestedLabel) {
            continue;
        }
        assertLabel(label);
        const parsed = parseBody(match[2]);
        blocks.push({
            label,
            data: base64.decode(parsed.base64Text),
            headers: parsed.headers,
            lineLength: parsed.base64Lines[0]?.length ?? 64,
            newline: detectNewline(match[0]),
        });
    }
    if (options.strict && blocks.length === 0) {
        throw new TypeError(requestedLabel
            ? `No PEM block with label '${requestedLabel}' was found`
            : "No PEM blocks were found");
    }
    return blocks;
}
export function encode(label, data, options = {}) {
    assertLabel(label);
    const lineLength = options.lineLength ?? 64;
    if (!Number.isInteger(lineLength) || lineLength < 1) {
        throw new RangeError("PEM lineLength must be a positive integer");
    }
    const newline = options.newline ?? "\n";
    const lines = [`-----BEGIN ${label}-----`];
    if (options.headers) {
        for (const [name, value] of Object.entries(options.headers)) {
            lines.push(`${name}: ${value}`);
        }
        lines.push("");
    }
    lines.push(...wrap(base64.encode(data), lineLength));
    lines.push(`-----END ${label}-----`);
    return `${lines.join(newline)}${newline}`;
}
export function encodeMany(blocks, options = {}) {
    return blocks.map((block) => encode(block.label, block.data, { ...options, headers: block.headers ?? options.headers })).join("");
}
export function decode(text, options = {}) {
    return collectBlocks(text, options).map(({ lineLength: _lineLength, newline: _newline, ...block }) => block);
}
export function find(text, label) {
    return decode(text, { label })[0];
}
export function findAll(text, label) {
    return decode(text, { label });
}
export function decodeFirst(text, label) {
    const [block] = decode(text, { label, strict: true });
    return block.data;
}
export function parse(text, options = {}) {
    const [block] = collectBlocks(text, { ...options, strict: true });
    const format = {
        label: block.label,
        headers: block.headers,
        lineLength: block.lineLength,
        newline: block.newline,
    };
    return {
        bytes: block.data,
        format,
        normalized: encode(block.label, block.data, format),
    };
}
export function format(data, value) {
    return encode(value.label, data, value);
}
export const pem = { decode, decodeFirst, encode, encodeMany, find, findAll, format, parse };
export const pemConverter = {
    name: "pem",
    encode: (data, options) => {
        if (!options?.label) {
            throw new TypeError("PEM label is required");
        }
        return encode(options.label, data, options);
    },
    decode: (text, options) => decodeFirst(text, options?.label),
    format,
    is: (text) => typeof text === "string" && /-----BEGIN [^-]+-----/.test(text),
    parse,
};