forked from olcxjas-softworks/LarpixClient
140 lines
4.7 KiB
JavaScript
140 lines
4.7 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.pemConverter = exports.pem = void 0;
|
|
exports.encode = encode;
|
|
exports.encodeMany = encodeMany;
|
|
exports.decode = decode;
|
|
exports.find = find;
|
|
exports.findAll = findAll;
|
|
exports.decodeFirst = decodeFirst;
|
|
exports.parse = parse;
|
|
exports.format = format;
|
|
const base64_js_1 = require("../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_js_1.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;
|
|
}
|
|
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_js_1.base64.encode(data), lineLength));
|
|
lines.push(`-----END ${label}-----`);
|
|
return `${lines.join(newline)}${newline}`;
|
|
}
|
|
function encodeMany(blocks, options = {}) {
|
|
return blocks.map((block) => encode(block.label, block.data, { ...options, headers: block.headers ?? options.headers })).join("");
|
|
}
|
|
function decode(text, options = {}) {
|
|
return collectBlocks(text, options).map(({ lineLength: _lineLength, newline: _newline, ...block }) => block);
|
|
}
|
|
function find(text, label) {
|
|
return decode(text, { label })[0];
|
|
}
|
|
function findAll(text, label) {
|
|
return decode(text, { label });
|
|
}
|
|
function decodeFirst(text, label) {
|
|
const [block] = decode(text, { label, strict: true });
|
|
return block.data;
|
|
}
|
|
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),
|
|
};
|
|
}
|
|
function format(data, value) {
|
|
return encode(value.label, data, value);
|
|
}
|
|
exports.pem = { decode, decodeFirst, encode, encodeMany, find, findAll, format, parse };
|
|
exports.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,
|
|
};
|