import { SendPreferences } from '@proton/shared/lib/interfaces/mail/crypto' ; import { SimpleMap } from '@proton/shared/lib/interfaces/utils' ; import { EncryptionPreferencesError } from '@proton/shared/lib/mail/encryptionPreferences' ; import { getRecipients , getRecipientsAddresses } from '@proton/shared/lib/mail/messages' ; import { useCallback } from 'react' ; import { c , msgid } from 'ttag' ; import { unique } from '@proton/shared/lib/helpers/array' ; import { useGetEncryptionPreferences , useModals , useNotifications } from '@proton/components' ; import { validateEmailAddress } from '@proton/shared/lib/helpers/email' ; import getSendPreferences from '@proton/shared/lib/mail/send/getSendPreferences' ; import { normalize } from '@proton/shared/lib/helpers/string' ; import { HOUR } from '@proton/shared/lib/constants' ; import { serverTime } from 'pmcrypto/lib/serverTime' ; import { languageCode , localeCode } from '@proton/shared/lib/i18n' ; import SendWithErrorsModal from '../../components/composer/addresses/SendWithErrorsModal' ; import { removeMessageRecipients , uniqueMessageRecipients } from '../../helpers/message/cleanMessage' ; import SendWithWarningsModal from '../../components/composer/addresses/SendWithWarningsModal' ; import SendWithExpirationModal from '../../components/composer/addresses/SendWithExpirationModal' ; import SendWithChangedPreferencesModal from '../../components/composer/addresses/SendWithChangedPreferencesModal' ; import { MapSendInfo } from '../../models/crypto' ; import { locateBlockquote } from '../../helpers/message/messageBlockquote' ; import { MESSAGE_ALREADY_SENT_INTERNAL_ERROR } from '../../constants' ; import { MessageStateWithData } from '../../logic/messages/messagesTypes' ; import { useGetMessage } from '../message/useMessage' ; import { useContactsMap } from '../contact/useContacts' ; const FR_REGEX = /voir pi\u00e8ce jointe|voir pi\u00e8ces jointes|voir fichier joint|voir fichiers joints|voir fichier associ\u00e9|voir fichiers associ\u00e9s|joint|joints|jointe|jointes|joint \u00e0 cet e-mail|jointe \u00e0 cet e-mail|joints \u00e0 cet e-mail|jointes \u00e0 cet e-mail|joint \u00e0 ce message|jointe \u00e0 ce message|joints \u00e0 ce message|jointes \u00e0 ce message|je joins|j'ai joint|ci-joint|pi\u00e8ce jointe|pi\u00e8ces jointes|fichier joint|fichiers joints|voir le fichier joint|voir les fichiers joints|voir la pi\u00e8ce jointe|voir les pi\u00e8ces jointes/gi ; const EN_REGEX = /see attached|see attachment|see included|is attached|attached is|are attached|attached are|attached to this email|attached to this message|I'm attaching|I am attaching|I've attached|I have attached|I attach|I attached|find attached|find the attached|find included|find the included|attached file|see the attached|see attachments|attached files|see the attachment|here is the attachment|attached you will find/gi ; const DE_REGEX = /siehe Anhang|angeh\u00e4ngt|anbei|hinzugef\u00fcgt|ist angeh\u00e4ngt|angeh\u00e4ngt ist|sind angeh\u00e4ngt|angeh\u00e4ngt sind|an diese E-Mail angeh\u00e4ngt|an diese Nachricht angeh\u00e4ngt|Anhang hinzuf\u00fcgen|Anhang anbei|Anhang hinzugef\u00fcgt|anbei finden|anbei|im Anhang|mit dieser E-Mail sende ich|angeh\u00e4ngte Datei|siehe angeh\u00e4ngte Datei|siehe Anh\u00e4nge|angeh\u00e4ngte Dateien|siehe Anlage|siehe Anlagen/gi ; const ES_REGEX = /ver adjunto|ver archivo adjunto|ver archivo incluido|se ha adjuntado|adjuntado|se han adjuntado|adjuntados|se ha adjuntado a este correo|se ha adjuntado a este mensaje|Adjunto te env\u00edo|He adjuntado|He adjuntado un archivo|adjunto|adjunto el archivo|incluyo el archivo|archivo adjunto|mira el archivo adjunto|ver archivos adjuntos|archivos adjuntos|ver el archivo adjunto/gi ; const RU_REGEX = /прикрепленный файл|прикреплённый файл|прикреплен|прикреплён|прикрепил|прикрепила/gi ; const IT_REGEX = /vedi in allegato|vedi allegato|vedi accluso|\u00e8 allegato|in allegato|sono allegati|in allegato vi sono|in allegato a questa email|in allegato a questo messaggio|invio in allegato|allego|ho allegato|in allegato trovi|trova in allegato|trova accluso|incluso troverai|file allegato|vedi allegato|vedi allegati|file allegati|vedi l'allegato|ti allego/gi ; const PT_PT_REGEX = /ver em anexo|ver anexo|ver inclu\u00eddo|est\u00e1 anexado|est\u00e1 em anexo|est\u00e3o anexados|est\u00e3o em anexo|anexado a este email|anexado a esta mensagem|estou a anexar|anexo|anexei|anexei|anexo|anexei|queira encontrar em anexo|segue em anexo|encontra-se inclu\u00eddo|segue inclu\u00eddo|ficheiro anexado|ver o anexo|ver anexos|ficheiros anexados|ver o anexo/gi ; const PT_BR_REGEX = /ver anexado|ver anexo|ver inclu\u00eddo|est\u00e1 anexado|anexado|est\u00e3o anexados|anexados|anexado a este e-mail|anexado a esta mensagem|estou anexando|eu estou anexando|anexei|eu anexei|estou incluindo|eu estou incluindo|inclu\u00ed|eu inclu\u00ed|enviar anexo|enviar o anexo|enviar inclu\u00eddo|ver anexos|arquivo anexado|arquivos anexados|em anexo|anexo|veja em anexo|veja o anexo|veja os anexos/gi ; const NL_REGEX = /zie bijgevoegd|zie bijlage|zie toegevoegd|is bijgevoegd|bijgevoegd is|zijn bijgevoegd|bijgevoegd zijn|toegevoegd aan dit e-mailbericht|toegevoegd aan dit bericht|Ik voeg bij|Ik heb bijgevoegd|Ik voeg een bijlage bij|Ik heb een bijlage bijgevoegd|bijlage bijvoegen|bijlagen bijvoegen|bijlage opgenomen|opgenomen bijlage|bijgevoegd bestand|zie de bijlage|zie bijlagen|bijgevoegde bestanden|de bijlage bekijken/gi ; const PL_REGEX = /patrz w za\u0142\u0105czeniu|patrz za\u0142\u0105cznik|patrz do\u0142\u0105czony|jest do\u0142\u0105czony|w za\u0142\u0105czeniu|s\u0105 za\u0142\u0105czone|za\u0142\u0105czone s\u0105|za\u0142\u0105czone do tego e-maila|do\u0142\u0105czone do tej wiadomo\u015bci|do\u0142\u0105czam|za\u0142\u0105czam|za\u0142\u0105czy\u0142em|za\u0142\u0105czy\u0142am|dodaj\u0119|wysy\u0142am|do\u0142\u0105czy\u0142em|do\u0142\u0105czy\u0142am|patrz za\u0142\u0105czniki|w za\u0142\u0105czniku|patrz za\u0142\u0105czony|patrz za\u0142\u0105czone|masz w za\u0142\u0105czeniu|za\u0142\u0105czony plik|zobacz za\u0142\u0105cznik|zobacz za\u0142\u0105czniki|za\u0142\u0105czone pliki/gi ; export const useSendVerifications = ( handleNoRecipients ? , handleNoSubjects ? , handleNoAttachments ? ) => { const { createModal } = useModals ( ) ; const { createNotification } = useNotifications ( ) ; const getEncryptionPreferences = useGetEncryptionPreferences ( ) ; const getMessage = useGetMessage ( ) ; const contactsMap = useContactsMap ( ) ; const preliminaryVerifications = useCallback ( async ( message ) : Promise < void > => { const { draftFlags } = getMessage ( message . localID ) as MessageStateWithData ; const { isSentDraft } = draftFlags || { } ; if ( isSentDraft ) { throw new Error ( MESSAGE_ALREADY_SENT_INTERNAL_ERROR ) ; } if ( ! getRecipients ( message . data ) . length ) { if ( handleNoRecipients ) { await handleNoRecipients ( ) ; } } if ( ! message . data . Subject ) { if ( handleNoSubjects ) { await handleNoSubjects ( ) ; } } const [ contentBeforeBlockquote ] = locateBlockquote ( message . messageDocument ?. document ) ; const normalized = normalize ( `${ message . data . Subject } ${ contentBeforeBlockquote || '' }` ) ; const [ keyword ] = ( languageCode === 'en' && normalized . match ( EN_REGEX ) ) || ( languageCode === 'fr' && normalized . match ( FR_REGEX ) ) || ( languageCode === 'de' && normalized . match ( DE_REGEX ) ) || ( languageCode === 'es' && normalized . match ( ES_REGEX ) ) || ( languageCode === 'ru' && normalized . match ( RU_REGEX ) ) || ( languageCode === 'it' && normalized . match ( IT_REGEX ) ) || ( languageCode === 'pt' && normalized . match ( PT_PT_REGEX ) ) || ( languageCode === 'pt' && localeCode === 'pt_BR' && normalized . match ( PT_BR_REGEX ) ) || ( languageCode === 'nl' && normalized . match ( NL_REGEX ) ) || ( languageCode === 'pl' && normalized . match ( PL_REGEX ) ) || [ ] ; if ( keyword && ! message . data . Attachments . length ) { if ( handleNoAttachments ) { await handleNoAttachments ( keyword ) ; } } } , [ ] ) ; const extendedVerifications = useCallback ( async ( message , trustedMapSendInfo ) : Promise < { cleanMessage : MessageStateWithData ; mapSendPrefs : SimpleMap < SendPreferences > ; hasChanged : boolean ; } > => { const uniqueMessage = { ... message , data : uniqueMessageRecipients ( message . data ) , } ; const emails = unique ( getRecipientsAddresses ( uniqueMessage . data ) ) ; const invalids = emails . filter ( ( email ) => ! validateEmailAddress ( email ) ) ; if ( invalids . length > 0 ) { const invalidAddresses = invalids . join ( ', ' ) ; createNotification ( { text : c ( 'Send email with warnings' ) . ngettext ( msgid `The following address is not valid: ${ invalidAddresses }` , `The following addresses are not valid: ${ invalidAddresses }` , invalids . length ) , type : 'error' , } ) ; throw new Error ( ) ; } const emailWarnings = { } ; const mapSendPrefs = { } ; const sendErrors = { } ; const emailsWithMissingPreferences = [ ] ; const expiresNotEncrypted = [ ] ; await Promise . all ( emails . map ( async ( email ) => { let sendPreferences ; const lastMinuteEncryptionPrefs = await getEncryptionPreferences ( email , 0 , contactsMap ) ; if ( lastMinuteEncryptionPrefs . emailAddressWarnings ?. length ) { emailWarnings [ email ] = lastMinuteEncryptionPrefs . emailAddressWarnings as string [ ] ; } const cachedSendInfo = trustedMapSendInfo [ email ] ; if ( ! cachedSendInfo || ! cachedSendInfo . contactSignatureInfo ?. isVerified ) { sendPreferences = getSendPreferences ( lastMinuteEncryptionPrefs , message . data ) ; } else if ( ! lastMinuteEncryptionPrefs . isContactSignatureVerified ) { if ( cachedSendInfo . sendPreferences ?. encrypt && cachedSendInfo . sendPreferences ?. isPublicKeyPinned ) { emailsWithMissingPreferences . push ( email ) ; } sendPreferences = getSendPreferences ( lastMinuteEncryptionPrefs , message . data ) ; } else { const cachedSignatureTime = cachedSendInfo . contactSignatureInfo . creationTime ! ; const lastMinuteSignatureTime = lastMinuteEncryptionPrefs . contactSignatureTimestamp ! ; const lastMinuteSignatureAge = Math . abs ( + lastMinuteSignatureTime - serverTime ( ) ) ; if ( + lastMinuteSignatureTime < + cachedSignatureTime || ( + lastMinuteSignatureTime !== + cachedSignatureTime && lastMinuteSignatureAge > 24 * HOUR ) ) { sendPreferences = cachedSendInfo . sendPreferences ! ; } else { sendPreferences = getSendPreferences ( lastMinuteEncryptionPrefs , message . data ) ; } } mapSendPrefs [ email ] = sendPreferences ; if ( sendPreferences . error ) { sendErrors [ email ] = sendPreferences . error ; } if ( message . draftFlags ?. expiresIn && ! sendPreferences . encrypt ) { expiresNotEncrypted . push ( email ) ; } } ) ) ; const emailsWithWarnings = Object . keys ( emailWarnings ) ; if ( emailsWithWarnings . length > 0 ) { await new Promise ( ( resolve , reject ) => { createModal ( < SendWithWarningsModal mapWarnings = { emailWarnings } onSubmit = { ( ) => resolve ( undefined ) } onClose = { reject } / > ) ; } ) ; } const emailsWithErrors = Object . keys ( sendErrors ) ; if ( emailsWithErrors . length > 0 ) { await new Promise ( ( resolve , reject ) => { const handleSendAnyway = ( ) => { for ( const email of emailsWithErrors ) { const indexOfEmail = emails . findIndex ( ( emailAddress ) => emailAddress === email ) ; emails . splice ( indexOfEmail , 1 ) ; delete mapSendPrefs [ email ] ; } resolve ( undefined ) ; } ; createModal ( < SendWithErrorsModal mapErrors = { sendErrors } cannotSend = { emailsWithErrors . length === emails . length } onSubmit = { handleSendAnyway } onClose = { reject } / > ) ; } ) ; } if ( emailsWithMissingPreferences . length > 0 ) { await new Promise ( ( resolve , reject ) => { createModal ( < SendWithChangedPreferencesModal emails = { emailsWithMissingPreferences } onSubmit = { ( ) => resolve ( undefined ) } onClose = { reject } / > ) ; } ) ; } if ( expiresNotEncrypted . length > 0 ) { await new Promise ( ( resolve , reject ) => { createModal ( < SendWithExpirationModal emails = { expiresNotEncrypted } onSubmit = { ( ) => resolve ( undefined ) } onClose = { reject } / > ) ; } ) ; } const cleanMessage = { ... message , data : removeMessageRecipients ( uniqueMessage . data , emailsWithErrors ) , } as MessageStateWithData ; return { cleanMessage , mapSendPrefs , hasChanged : emailsWithErrors . length > 0 } ; } , [ contactsMap ] ) ; return { preliminaryVerifications , extendedVerifications } ; } ;