n8n
Version:
n8n Workflow Automation Tool
167 lines • 6.96 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TarPackageReader = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const node_path_1 = __importDefault(require("node:path"));
const tar_1 = require("tar");
const bad_request_error_1 = require("../../../../errors/response-errors/bad-request.error");
const MANIFEST_PATH = 'manifest.json';
const ALLOWED_PATH_CHARS = /^[a-zA-Z0-9._/-]+$/;
class TarPackageReader {
constructor(buffer, limits) {
this.buffer = buffer;
this.limits = limits;
this.entries = null;
}
async readManifest() {
const entries = await this.load();
const manifest = entries.get(MANIFEST_PATH);
if (!manifest) {
throw new bad_request_error_1.BadRequestError('Package is missing manifest.json');
}
try {
return (0, n8n_workflow_1.jsonParse)(manifest.toString('utf-8'));
}
catch {
throw new bad_request_error_1.BadRequestError('Package manifest is not valid JSON');
}
}
async readFile(entryPath) {
const entries = await this.load();
const content = entries.get(entryPath);
if (!content) {
throw new bad_request_error_1.BadRequestError(`Package does not contain entry: ${entryPath}`);
}
return content;
}
async listEntries() {
const entries = await this.load();
return Array.from(entries.keys());
}
async load() {
if (this.entries)
return this.entries;
this.entries = await this.parse();
return this.entries;
}
validateEntryPath(rawPath) {
const trimmed = rawPath.endsWith('/') ? rawPath.slice(0, -1) : rawPath;
if (trimmed.length === 0) {
throw new bad_request_error_1.BadRequestError('Package contains an entry with an empty path');
}
if (trimmed.length > this.limits.maxPathLength) {
throw new bad_request_error_1.BadRequestError('Package entry path exceeds the maximum allowed length');
}
if (trimmed.startsWith('/')) {
throw new bad_request_error_1.BadRequestError(`Package entry path "${trimmed}" must be relative`);
}
if (!ALLOWED_PATH_CHARS.test(trimmed)) {
throw new bad_request_error_1.BadRequestError(`Package entry path "${trimmed}" contains disallowed characters`);
}
const normalized = node_path_1.default.posix.normalize(trimmed);
if (normalized === '..' ||
normalized.startsWith('../') ||
normalized.includes('/../') ||
normalized.endsWith('/..')) {
throw new bad_request_error_1.BadRequestError(`Package entry path "${trimmed}" attempts to escape the package root`);
}
return normalized;
}
async parse() {
const { maxEntries, maxEntryBytes, maxUncompressedBytes } = this.limits;
const entries = new Map();
let totalUncompressedBytes = 0;
let entryCount = 0;
let firstFileSeen = false;
return await new Promise((resolve, reject) => {
const parser = new tar_1.Parser({ strict: true });
let aborted = false;
const fail = (message) => {
if (aborted)
return;
aborted = true;
try {
parser.abort(new Error(message));
}
catch {
}
reject(new bad_request_error_1.BadRequestError(message));
};
const accept = (entry) => {
if (++entryCount > maxEntries) {
throw new bad_request_error_1.BadRequestError('Package contains too many entries');
}
if (entry.type !== 'File' && entry.type !== 'Directory') {
throw new bad_request_error_1.BadRequestError(`Package contains a disallowed entry type for "${entry.path}"`);
}
const safePath = this.validateEntryPath(entry.path);
if (entries.has(safePath)) {
throw new bad_request_error_1.BadRequestError(`Package contains a duplicate entry for "${safePath}"`);
}
if (entry.type === 'Directory')
return null;
if (!firstFileSeen) {
firstFileSeen = true;
if (safePath !== MANIFEST_PATH) {
throw new bad_request_error_1.BadRequestError(`Package must begin with ${MANIFEST_PATH} but found "${safePath}"`);
}
}
return safePath;
};
parser.on('entry', (entry) => {
let validated = null;
if (!aborted) {
try {
validated = accept(entry);
}
catch (error) {
fail(error instanceof bad_request_error_1.BadRequestError ? error.message : 'Invalid package entry path');
}
}
if (validated === null) {
entry.resume();
return;
}
const safePath = validated;
const chunks = [];
let entryBytes = 0;
entry.on('data', (chunk) => {
if (aborted)
return;
entryBytes += chunk.length;
if (entryBytes > maxEntryBytes) {
fail(`Package entry "${safePath}" exceeds the maximum allowed uncompressed size per entry`);
return;
}
totalUncompressedBytes += chunk.length;
if (totalUncompressedBytes > maxUncompressedBytes) {
fail('Package exceeds the maximum allowed uncompressed size');
return;
}
chunks.push(chunk);
});
entry.on('end', () => {
if (!aborted)
entries.set(safePath, Buffer.concat(chunks));
});
entry.resume();
});
parser.on('error', () => {
if (aborted)
return;
aborted = true;
reject(new bad_request_error_1.BadRequestError('Failed to read package archive'));
});
parser.on('end', () => {
if (!aborted)
resolve(entries);
});
parser.end(this.buffer);
});
}
}
exports.TarPackageReader = TarPackageReader;
//# sourceMappingURL=tar-package-reader.js.map