"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.copyIcons = copyIcons; exports.copyMimeTypes = copyMimeTypes; const path = require("path"); const fs = require("fs-extra"); const builder_util_1 = require("builder-util"); const ICON_DIR_RELATIVE_PATH = "usr/share/icons/hicolor"; const MIME_TYPE_DIR_RELATIVE_PATH = "usr/share/mime/packages"; /** * Escapes special XML characters to prevent injection */ function xmlEscape(str) { return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } async function copyIcons(options) { const { stageDir, options: configuration } = options; const iconCommonDir = path.join(stageDir, ICON_DIR_RELATIVE_PATH); await fs.ensureDir(iconCommonDir); const icons = configuration.icons; if (!icons || icons.length === 0) { throw new Error("At least one icon is required for AppImage"); } const iconExtWithDot = path.extname(icons[0].file); const iconFileName = `${configuration.executableName}${iconExtWithDot}`; const maxIconIndex = icons.length - 1; const iconInfoList = icons.map(icon => { if (path.extname(icon.file) !== iconExtWithDot) { throw new Error(`All icons must have the same extension: expected ${iconExtWithDot}, but got ${icon.file}`); } let iconSizeDir; if (iconExtWithDot === ".svg") { // SVG icons go in scalable/apps directory per freedesktop icon theme spec iconSizeDir = "scalable/apps"; } else { iconSizeDir = `${icon.size}x${icon.size}/apps`; } const iconRelativeToStageFile = path.join(ICON_DIR_RELATIVE_PATH, iconSizeDir, iconFileName); const iconDir = path.join(iconCommonDir, iconSizeDir); const iconFile = path.join(iconDir, iconFileName); return { icon, iconDir, iconFile, iconRelativeToStageFile }; }); await Promise.all(iconInfoList.map(async ({ icon, iconDir, iconFile }) => { await fs.ensureDir(iconDir); await (0, builder_util_1.copyOrLinkFile)(icon.file, iconFile); })); // Create symlinks for the last (largest) icon const { iconRelativeToStageFile } = iconInfoList[maxIconIndex]; await fs.symlink(iconRelativeToStageFile, path.join(stageDir, iconFileName)); await fs.symlink(iconRelativeToStageFile, path.join(stageDir, ".DirIcon")); } async function copyMimeTypes(options) { const { stageDir, options: { fileAssociations, productName, executableName }, } = options; if (!fileAssociations || fileAssociations.length === 0) { return null; } const mimeTypeParts = []; for (const fileAssociation of fileAssociations) { if (!fileAssociation.mimeType) { continue; } // XML-escape to prevent injection mimeTypeParts.push(``); mimeTypeParts.push(` ${xmlEscape(productName)} document`); // Handle extension(s) const extensions = Array.isArray(fileAssociation.ext) ? fileAssociation.ext : [fileAssociation.ext]; for (const ext of extensions) { // Validate extension doesn't contain dangerous characters if (!/^[a-zA-Z0-9_-]+$/.test(ext)) { builder_util_1.log.warn({ extension: ext }, `file extension contains unexpected characters and may not be supported`); } mimeTypeParts.push(` `); } mimeTypeParts.push(' '); mimeTypeParts.push(""); } // If no mime-types were generated, return null if (mimeTypeParts.length === 0) { return null; } const mimeTypeDir = path.join(stageDir, MIME_TYPE_DIR_RELATIVE_PATH); const fileName = `${executableName}.xml`; const mimeTypeFile = path.join(mimeTypeDir, fileName); await fs.ensureDir(mimeTypeDir); const xmlContent = ['', '', ...mimeTypeParts, ""].join("\n"); // Use 0o644 (rw-r--r--) instead of 0o666 to avoid world-writable permissions await fs.writeFile(mimeTypeFile, xmlContent, { mode: 0o644 }); return path.join(MIME_TYPE_DIR_RELATIVE_PATH, fileName); } //# sourceMappingURL=appLauncher.js.map