Skip to content
Snippets Groups Projects
MimeVerificationService.js 6.55 KiB
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const normalizationStrategies_1 = require("./normalizationStrategies");
const MailParser_1 = require("../MailParser/MailParser");
const CryptoService_1 = require("../CryptoService");
const StatusesService_1 = require("../StatusesService");
const index_1 = require("./index");
const rka_1 = require("../../utils/rka");
const common_1 = require("../../utils/common");
const VerificationError_1 = require("../VerificationService/VerificationError");
const stringUtils_1 = require("../../utils/stringUtils");
const utils_1 = require("./utils");
const vendorAmendingFunctions = {
    [StatusesService_1.EMAIL_VENDORS.GMAIL]: normalizationStrategies_1.amendGmailNodes,
    [StatusesService_1.EMAIL_VENDORS.OUTLOOK]: normalizationStrategies_1.amendOutlookNodes,
};
const vendorPruningFunctions = {
    [StatusesService_1.EMAIL_VENDORS.GMAIL]: normalizationStrategies_1.pruneGmailElement,
    [StatusesService_1.EMAIL_VENDORS.OUTLOOK]: normalizationStrategies_1.pruneOutlookElement,
};
const vendorAttributesCleanupFunctions = {
    [StatusesService_1.EMAIL_VENDORS.GMAIL]: normalizationStrategies_1.cleanupGMailElementAttributes,
    [StatusesService_1.EMAIL_VENDORS.OUTLOOK]: normalizationStrategies_1.cleanupOutlookElementAttributes,
};
const vendorPrintingFunctions = {
    [StatusesService_1.EMAIL_VENDORS.OUTLOOK]: utils_1.printOutlookElement,
};
// Load JSDOM dynamically for Node env only, because built CRA is crashing with it
let JSDOM;
const loadJSDOM = () => __awaiter(void 0, void 0, void 0, function* () {
    if (!JSDOM) {
        JSDOM = (yield Promise.resolve().then(() => require("jsdom"))).JSDOM;
    }
});
typeof window === "undefined" && loadJSDOM();
const prepareMimeForPartsVerification = (mimeString, attachmentsHashes, senderSystem, sourceHtmlRabinFingerprint, sourceHtmlSize, sourcePlainRabinFingerprint, sourcePlainSize) => __awaiter(void 0, void 0, void 0, function* () {
    const { htmlPart, plainPart } = getNormalizedMimeParts(mimeString, senderSystem);
    const originalHtml = findSubstring(htmlPart, sourceHtmlRabinFingerprint, sourceHtmlSize);
    if (originalHtml === null) {
        throw new VerificationError_1.default("Unable to verify MIME. Source HTML part was not found.");
    }
    const originalPlain = findSubstring(plainPart, sourcePlainRabinFingerprint, sourcePlainSize);
    if (originalPlain === null) {
        throw new VerificationError_1.default("Unable to verify MIME. Source Plain part was not found.");
    }
    return yield index_1.default.preparePartsForSigning([originalHtml, originalPlain], attachmentsHashes);
});
const getNormalizedMimeParts = (mimeString, senderSystem) => {
    const { htmlPart, plainPart, } = index_1.default.getMimeHtmlAndPlainParts(mimeString);
    const normalizedHtmlPart = index_1.default.normalizeVendorHtml(htmlPart, senderSystem);
    const normalizedPlainPart = stringUtils_1.removeSpacesAndLinebreaks(plainPart);
    return {
        htmlPart: normalizedHtmlPart,
        plainPart: normalizedPlainPart,
    };
};
const findSubstring = (string, rabinFingerprint, substringSize) => {
    if (!string.length) {
        return "";
    }
    const startingIndex = rka_1.default.searchFingerprintInText(string, rabinFingerprint, substringSize);
    if (startingIndex === -1) {
        return null;
    }
    return string.slice(startingIndex, startingIndex + substringSize);
};
const getMimeHtmlAndPlainParts = (mimeString) => {
    mimeString = MailParser_1.fixNewLines(mimeString);
    const mimeParts = MailParser_1.default.parseMIME(mimeString);
    // TODO: check, whether we need to support 2+ html/plain MIME parts.
    const htmlPart = MailParser_1.default.getHTML(mimeString, mimeParts);
    const plainPart = MailParser_1.default.getPlain(mimeString, mimeParts);
    return {
        htmlPart,
        plainPart,
    };
};
/**
 * @param parts - html and plain parts strings
 * @param attachmentsHashes - base64 strings of the attachments hashes
 */
const preparePartsForSigning = (parts, attachmentsHashes) => __awaiter(void 0, void 0, void 0, function* () {
    let hashedParts = yield Promise.all(parts.map((part) => __awaiter(void 0, void 0, void 0, function* () {
        return common_1.arrayBufferToBase64(yield CryptoService_1.default.SHA256(part));
    })));
    hashedParts = [...hashedParts, ...attachmentsHashes];
    return hashedParts.sort().join("\n");
});
const normalizeVendorHtml = (htmlString, vendor) => {
    let document;
    if (typeof DOMParser !== "undefined") {
        const parser = new DOMParser();
        document = parser.parseFromString(htmlString, "text/html");
    }
    else {
        const { window } = new JSDOM(htmlString);
        document = window.document;
    }
    const mimeBody = document.body;
    const amendNodesFunction = vendorAmendingFunctions[vendor];
    if (amendNodesFunction) {
        amendNodesFunction(document);
    }
    /**
     * Remove unnecessary nodes
     */
    const elementPruningFunction = vendorPruningFunctions[vendor];
    if (!elementPruningFunction) {
        throw new Error(`Vendor "${vendor}" is not supported. Please, develop a pruning function for it.`);
    }
    utils_1.pruneHtmlNode(document, elementPruningFunction);
    /**
     * Cleanup unnecessary attributes of nodes
     */
    const elementAttributesCleanupFunction = vendorAttributesCleanupFunctions[vendor];
    if (elementAttributesCleanupFunction) {
        utils_1.cleanupHtmlNodeAttributes(document, elementAttributesCleanupFunction);
    }
    /**
     * Print nodes
     */
    const vendorPrintFunction = vendorPrintingFunctions[vendor];
    return utils_1.printHtmlChildren(mimeBody, vendorPrintFunction, 0);
};
const normalizePlainPart = (text) => {
    return stringUtils_1.removeSpacesAndLinebreaks(text);
};
exports.default = {
    normalizeVendorHtml,
    getMimeHtmlAndPlainParts,
    preparePartsForSigning,
    prepareMimeForPartsVerification,
    getNormalizedMimeParts,
    normalizePlainPart,
};