import { unique } from '@proton/shared/lib/helpers/array' ; import { getEmailParts } from '@proton/shared/lib/helpers/email' ; import generateUID from '@proton/shared/lib/helpers/generateUID' ; import { Attachment } from '@proton/shared/lib/interfaces/mail/Message' ; import { ENCRYPTED_STATUS } from '../../constants' ; import { LoadEmbeddedResults , MessageEmbeddedImage , PartialMessageState } from '../../logic/messages/messagesTypes' ; import { UploadResult } from '../attachment/attachmentUploader' ; import { hash , toUnsignedString } from '../string' ; import { querySelectorAll } from './messageContent' ; import { getEmbeddedImages , updateImages } from './messageImages' ; const urlCreator = ( ) => window . URL || window . webkitURL ; export const embeddableTypes = [ 'image/gif' , 'image/jpeg' , 'image/png' , 'image/bmp' ] ; /**
 * Removes enclosing quotes ("", '', &lt;&gt;) from a string
 */ const trimQuotes = ( input ) => { const value = `${ input || '' }` . trim ( ) ; if ( [ '"' , "'" , '<' ] . indexOf ( value . charAt ( 0 ) ) > - 1 && [ '"' , "'" , '>' ] . indexOf ( value . charAt ( value . length - 1 ) ) > - 1 ) { return value . substr ( 1 , value . length - 2 ) ; } return value ; } ; /**
 * Is a file type embeddable or not
 */ export const isEmbeddable = ( fileType ) => embeddableTypes . includes ( fileType ) ; /**
 * Read CID of the attachment (through its header)
 */ export const readContentIDandLocation = ( { Headers = { } } = { } ) => { let cid = '' ; let cloc = '' ; if ( Headers [ 'content-id' ] ) { cid = trimQuotes ( Headers [ 'content-id' ] ) ; } if ( Headers [ 'content-location' ] ) { cloc = trimQuotes ( Headers [ 'content-location' ] ) ; } return { cid , cloc } ; } ; export const matchSameCidOrLoc = ( image , cid , cloc ) => ( cid !== '' && image . cid === cid ) || ( cloc !== '' && image . cloc === cloc ) ; /**
 * Generate CID from input and email
 */ export const generateCid = ( input , email ) => { const hashValue = toUnsignedString ( hash ( input ) , 4 ) ; const [ , domain ] = getEmailParts ( email ) ; return `${ hashValue }@${ domain }` ; } ; export const setEmbeddedAttr = ( cid , cloc , element ) => { if ( cid ) { element . setAttribute ( 'data-embedded-img' , `cid:${ cid }` ) ; } else if ( cloc ) { element . setAttribute ( 'data-embedded-img' , `cloc:${ cloc }` ) ; } } ; /**
 * Create a Blob and its URL for an attachment
 */ export const createBlob = ( attachment , data ) => { const blob = new Blob ( [ data ] , { type : attachment . MIMEType } ) ; return urlCreator ( ) . createObjectURL ( blob ) ; } ; /**
 * Prepare MessageEmbeddedImage structure based on an upload result
 */ export const createEmbeddedImageFromUpload = ( upload ) : MessageEmbeddedImage => { const { cid , cloc } = readContentIDandLocation ( upload . attachment ) ; return { type : 'embedded' , id : generateUID ( 'embedded' ) , cid , cloc , tracker : undefined , attachment : upload . attachment , status : 'loaded' , } ; } ; /**
 * Find embedded element in div
 */ export const findEmbedded = ( cid , cloc , document ) => { let selector = [ ] ; if ( ! cid && ! cloc ) { return [ ] ; } if ( cid ) { selector = [ `img[src="${ cid }"]` , `img[src="cid:${ cid }"]` , `img[data-embedded-img="${ cid }"]` , `img[data-embedded-img="cid:${ cid }"]` , `img[data-src="cid:${ cid }"]` , `img[proton-src="cid:${ cid }"]` , ] ; } if ( cloc ) { selector = [ ... selector , `img[proton-src="${ cloc }"]` ] ; } return querySelectorAll ( { messageDocument : { document } } , selector . join ( ', ' ) ) ; } ; /**
 * Find CIDs in content diff from the editor to detect embedded deletion
 * (with compat mode for old embedded)
 */ export const findCIDsInContent = ( content ) => ( content . match ( /(rel=("([^"]|"")*"))|(data-embedded-img=("([^"]|"")*"))/g ) || [ ] ) . filter ( ( key ) => key !== 'rel="noreferrer nofollow noopener"' ) . map ( ( key ) => key . replace ( /rel="|data-embedded-img="/ , '' ) ) . map ( ( key ) => key . slice ( 4 , - 1 ) ) ; /**
 * Insert actual src="cid:..." into embedded image elements
 */ export const insertActualEmbeddedImages = ( document ) => { querySelectorAll ( { messageDocument : { document } } , '[data-embedded-img]' ) . forEach ( ( element ) => { const cidOrCloc = element . getAttribute ( 'data-embedded-img' ) ; element . removeAttribute ( 'data-embedded-img' ) ; element . removeAttribute ( 'proton-src' ) ; element . removeAttribute ( 'src' ) ; if ( cidOrCloc ?. startsWith ( 'cid' ) ) { element . setAttribute ( 'src' , `cid:${ cidOrCloc . slice ( 4 ) }` ) ; } else if ( cidOrCloc ?. startsWith ( 'cloc' ) ) { element . setAttribute ( 'src' , cidOrCloc . slice ( 5 ) ) ; } } ) ; } ; /**
 * Replace attachments in embedded images with new ones
 */ export const replaceEmbeddedAttachments = ( message , attachments = [ ] ) => { const embeddedImages = getEmbeddedImages ( message ) ; const newEmbeddedImages = embeddedImages . map ( ( image ) => { const attachment = attachments . find ( ( attachment ) => { const { cid , cloc } = readContentIDandLocation ( attachment ) ; return matchSameCidOrLoc ( image , cid , cloc ) ; } ) ; if ( attachment ) { return { ... image , attachment } ; } return image ; } ) ; return updateImages ( message . messageImages , undefined , undefined , newEmbeddedImages ) ; } ; /**
 * Remove an embedded attachment from the document
 */ export const removeEmbeddedHTML = ( document , attachment ) => { const { cid , cloc } = readContentIDandLocation ( attachment ) ; const elements = findEmbedded ( cid , cloc , document ) ; elements . map ( ( node ) => node . remove ( ) ) ; } ; /**
 * Set blob url src on embedded images of the document
 */ export const insertBlobImages = ( document , embeddedImages ) => { embeddedImages . forEach ( ( embeddedImage ) => { const { cid , cloc , url } = embeddedImage ; if ( url ) { findEmbedded ( cid , cloc , document ) . forEach ( ( element ) => { element . removeAttribute ( 'proton-src' ) ; element . setAttribute ( 'src' , url ) ; } ) ; } } ) ; } ; /**
 * Based on the loadResults, update the embeddedImages with a url and the loaded state
 */ export const markEmbeddedImagesAsLoaded = ( embeddedImages , loadResults ) => { return embeddedImages . map ( ( image ) => { const result = loadResults . find ( ( loadResult ) => loadResult . attachment . ID === image . attachment . ID ) ; if ( result ) { return { ... image , url : result ?. blob , status : 'loaded' as 'loaded' } ; } return image ; } ) ; } ; /**
 * Download and decrypt embedded images
 */ export const decryptEmbeddedImages = ( images , onLoadEmbeddedImages ) => { const attachments = unique ( images . filter ( ( { status } ) => status === 'not-loaded' ) . filter ( ( info ) => info . attachment . KeyPackets || info . attachment . Encrypted === ENCRYPTED_STATUS . PGP_MIME ) . map ( ( image ) => ( image as MessageEmbeddedImage ) . attachment ) ) ; const updatedImages = images . map ( ( image ) => { if ( attachments . includes ( image . attachment ) && image . status === 'not-loaded' ) { return { ... image , status : 'loading' } as MessageEmbeddedImage ; } return image ; } ) ; const downloadPromise = onLoadEmbeddedImages ( attachments ) ; return { updatedImages , downloadPromise } ; } ;