import { createElement } from 'Common/Globals'; import { forEachObjectEntry, pInt } from 'Common/Utils'; import { SettingsUserStore } from 'Stores/User/Settings'; const tpl = createElement('template'), htmlre = /[&<>"']/g, httpre = /^(https?:)?\/\//i, htmlmap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }, replaceWithChildren = node => node.replaceWith(...[...node.childNodes]), // Strip tracking urlGetParam = (url, name) => new URL(url).searchParams.get(name) || url, base64Url = data => atob(data.replace(/_/g,'/').replace(/-/g,'+')), stripTracking = text => text .replace(/tracking\.(printabout\.nl[^?]+)\?.*/i, (...m) => m[1]) .replace(/^.+awstrack\.me\/.+(https:%2F%2F[^/]+)/i, (...m) => decodeURIComponent(m[1])) .replace(/^.+(www\.google|safelinks\.protection\.outlook\.com).+$/i, () => urlGetParam(text, 'url')) .replace(/^.+go\.dhlparcel\.nl.+\/([^/]+)$/i, (...m) => base64Url(m[1])) // Mandrill .replace(/^.+\/track\/click\/.+\?p=.+$/i, () => { let d = urlGetParam(text, 'p'); try { d = JSON.parse(base64Url(d)); if (d?.p) { d = JSON.parse(d.p); } } catch (e) { console.error(e); } return d?.url || text; }) .replace(/([?&])utm_[a-z]+=[^&?#]*/gi, '$1') // Urchin Tracking Module .replace(/([?&])ec_[a-z]+=[^&?#]*/gi, '$1') // Sitecore /** TODO: implement other url strippers like from * https://www.bleepingcomputer.com/news/security/new-firefox-privacy-feature-strips-urls-of-tracking-parameters/ * https://github.com/newhouse/url-tracking-stripper * https://github.com/svenjacobs/leon * https://maxchadwick.xyz/tracking-query-params-registry/ * https://github.com/M66B/FairEmail/blob/master/app/src/main/java/eu/faircode/email/UriHelper.java */ .replace(/([?&])&+/g, '$1'); export const /** * @param {string} text * @returns {string} */ encodeHtml = text => (text?.toString?.() || '' + text).replace(htmlre, m => htmlmap[m]), /** * Clears the Message Html for viewing * @param {string} text * @returns {string} */ cleanHtml = (html, oAttachments) => { const debug = false, // Config()->Get('debug', 'enable', false); detectHiddenImages = true, // !!SettingsGet('try_to_detect_hidden_images'), result = { hasExternals: false }, findAttachmentByCid = cid => oAttachments.findByCid(cid), findLocationByCid = cid => { const attachment = findAttachmentByCid(cid); return attachment?.contentLocation ? attachment : 0; }, // convert body attributes to CSS tasks = { link: value => { if (/^#[a-fA-Z0-9]{3,6}$/.test(value)) { tpl.content.querySelectorAll('a').forEach(node => node.style.color = value) } }, text: (value, node) => node.style.color = value, topmargin: (value, node) => node.style.marginTop = pInt(value) + 'px', leftmargin: (value, node) => node.style.marginLeft = pInt(value) + 'px', bottommargin: (value, node) => node.style.marginBottom = pInt(value) + 'px', rightmargin: (value, node) => node.style.marginRight = pInt(value) + 'px' }, allowedAttributes = [ // defaults 'name', 'dir', 'lang', 'style', 'title', 'background', 'bgcolor', 'alt', 'height', 'width', 'src', 'href', 'border', 'bordercolor', 'charset', 'direction', // a 'download', 'hreflang', // body 'alink', 'bottommargin', 'leftmargin', 'link', 'rightmargin', 'text', 'topmargin', 'vlink', // col 'align', 'valign', // font 'color', 'face', 'size', // hr 'noshade', // img 'hspace', 'sizes', 'srcset', 'vspace', // meter 'low', 'high', 'optimum', 'value', // ol 'reversed', 'start', // table 'cols', 'rows', 'frame', 'rules', 'summary', 'cellpadding', 'cellspacing', // th 'abbr', 'scope', // td 'colspan', 'rowspan', 'headers' ], disallowedTags = [ 'HEAD','STYLE','SVG','SCRIPT','TITLE','LINK','BASE','META', 'INPUT','OUTPUT','SELECT','BUTTON','TEXTAREA', 'BGSOUND','KEYGEN','SOURCE','OBJECT','EMBED','APPLET','IFRAME','FRAME','FRAMESET','VIDEO','AUDIO','AREA','MAP' ], nonEmptyTags = [ 'A','B','EM','I','SPAN','STRONG' ]; tpl.innerHTML = html // .replace(/]*>[\s\S]*?<\/pre>/gi, pre => pre.replace(/\n/g, '\n
')) .replace(/]*>/gi, '') .replace(/<\?xml[^>]*\?>/gi, '') // Not supported by