2014-10-02 16:05:44 -04:00
'use strict' ;
2014-11-20 16:53:30 -05:00
var ngModule = angular . module ( 'woEmail' ) ;
2014-11-18 12:44:00 -05:00
ngModule . service ( 'email' , Email ) ;
module . exports = Email ;
2014-11-06 08:28:14 -05:00
var config = require ( '../app-config' ) . config ,
2015-01-22 06:07:14 -05:00
str = require ( '../app-config' ) . string ,
axe = require ( 'axe-logger' ) ,
PgpMailer = require ( 'pgpmailer' ) ,
ImapClient = require ( 'imap-client' ) ;
2014-10-02 16:05:44 -04:00
//
//
// Constants
//
//
var FOLDER _DB _TYPE = 'folders' ;
var SYNC _TYPE _NEW = 'new' ;
var SYNC _TYPE _DELETED = 'deleted' ;
var SYNC _TYPE _MSGS = 'messages' ;
2014-10-16 06:23:49 -04:00
// well known folders
2014-10-02 16:05:44 -04:00
var FOLDER _TYPE _INBOX = 'Inbox' ;
var FOLDER _TYPE _SENT = 'Sent' ;
var FOLDER _TYPE _DRAFTS = 'Drafts' ;
var FOLDER _TYPE _TRASH = 'Trash' ;
2014-10-16 06:23:49 -04:00
var FOLDER _TYPE _FLAGGED = 'Flagged' ;
2014-10-02 16:05:44 -04:00
var MSG _ATTR _UID = 'uid' ;
2015-03-31 09:43:42 -04:00
var MSG _ATTR _MODSEQ = 'modseq' ;
2014-10-02 16:05:44 -04:00
var MSG _PART _ATTR _CONTENT = 'content' ;
var MSG _PART _TYPE _ATTACHMENT = 'attachment' ;
var MSG _PART _TYPE _ENCRYPTED = 'encrypted' ;
var MSG _PART _TYPE _SIGNED = 'signed' ;
var MSG _PART _TYPE _TEXT = 'text' ;
var MSG _PART _TYPE _HTML = 'html' ;
//
//
2014-11-18 12:44:00 -05:00
// Email Service
2014-10-02 16:05:44 -04:00
//
//
/ * *
* High - level data access object that orchestrates everything around the handling of encrypted mails :
* PGP de - / e n c r y p t i o n , r e c e i v i n g v i a I M A P , s e n d i n g v i a S M T P , M I M E p a r s i n g , l o c a l d b p e r s i s t e n c e
*
* @ param { Object } keychain The keychain DAO handles keys transparently
* @ param { Object } pgp Orchestrates decryption
* @ param { Object } devicestorage Handles persistence to the local indexed db
* @ param { Object } pgpbuilder Generates and encrypts MIME and SMTP messages
* @ param { Object } mailreader Parses MIME messages received from IMAP
* /
2015-01-22 06:07:14 -05:00
function Email ( keychain , pgp , accountStore , pgpbuilder , mailreader , dialog , appConfig , auth ) {
2014-10-02 16:05:44 -04:00
this . _keychain = keychain ;
this . _pgp = pgp ;
2014-11-20 09:14:39 -05:00
this . _devicestorage = accountStore ;
2014-10-02 16:05:44 -04:00
this . _pgpbuilder = pgpbuilder ;
this . _mailreader = mailreader ;
2014-11-21 11:25:55 -05:00
this . _dialog = dialog ;
2015-01-22 06:07:14 -05:00
this . _appConfig = appConfig ;
this . _auth = auth ;
2014-11-18 12:44:00 -05:00
}
2014-10-02 16:05:44 -04:00
//
//
// Public API
//
//
/ * *
* Initializes the email dao :
2014-11-06 08:28:14 -05:00
* - assigns _account
2014-10-02 16:05:44 -04:00
* - initializes _account . folders with the content from memory
*
* @ param { String } options . account . emailAddress The user ' s id
2014-11-06 08:28:14 -05:00
* @ param { String } options . account . realname The user ' s id
2014-12-15 13:33:02 -05:00
* @ return { Promise }
* @ resolve { Object } keypair
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . init = function ( options ) {
2015-03-31 09:43:42 -04:00
var self = this ;
2013-08-16 14:50:47 -04:00
2015-03-31 09:43:42 -04:00
self . _account = options . account ;
self . _account . busy = 0 ; // >0 triggers the spinner
self . _account . online = false ;
self . _account . loggingIn = false ;
// fetch folders from idb
return self . _devicestorage . listItems ( FOLDER _DB _TYPE , true ) . then ( function ( stored ) {
self . _account . folders = stored [ 0 ] || [ ] ;
return self . _initFolders ( ) ;
} ) ;
2014-10-02 16:05:44 -04:00
} ;
/ * *
* Unlocks the keychain by either decrypting an existing private key or generating a new keypair
* @ param { String } options . passphrase The passphrase to decrypt the private key
* /
2014-12-15 13:33:02 -05:00
Email . prototype . unlock = function ( options ) {
var self = this ,
generatedKeypair ;
2014-10-02 16:05:44 -04:00
if ( options . keypair ) {
// import existing key pair into crypto module
2014-12-15 13:33:02 -05:00
return handleExistingKeypair ( options . keypair ) ;
2014-10-02 16:05:44 -04:00
}
// no keypair for is stored for the user... generate a new one
2014-12-15 13:33:02 -05:00
return self . _pgp . generateKeys ( {
2014-10-02 16:05:44 -04:00
emailAddress : self . _account . emailAddress ,
2015-01-20 06:55:19 -05:00
realname : options . realname ,
2014-10-02 16:05:44 -04:00
keySize : self . _account . asymKeySize ,
passphrase : options . passphrase
2014-12-15 13:33:02 -05:00
} ) . then ( function ( keypair ) {
generatedKeypair = keypair ;
// import the new key pair into crypto module
return self . _pgp . importKeys ( {
passphrase : options . passphrase ,
privateKeyArmored : generatedKeypair . privateKeyArmored ,
publicKeyArmored : generatedKeypair . publicKeyArmored
} ) ;
2013-12-03 13:21:50 -05:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
// persist newly generated keypair
2015-02-20 11:55:11 -05:00
return {
2014-12-15 13:33:02 -05:00
publicKey : {
_id : generatedKeypair . keyId ,
userId : self . _account . emailAddress ,
publicKey : generatedKeypair . publicKeyArmored
} ,
privateKey : {
_id : generatedKeypair . keyId ,
userId : self . _account . emailAddress ,
encryptedKey : generatedKeypair . privateKeyArmored
}
} ;
2013-12-03 13:21:50 -05:00
2014-12-15 13:33:02 -05:00
} ) . then ( setPrivateKey ) ;
2014-10-02 16:05:44 -04:00
2014-12-15 13:33:02 -05:00
function handleExistingKeypair ( keypair ) {
2014-12-16 08:12:30 -05:00
return new Promise ( function ( resolve ) {
2014-12-15 13:33:02 -05:00
var privKeyParams = self . _pgp . getKeyParams ( keypair . privateKey . encryptedKey ) ;
var pubKeyParams = self . _pgp . getKeyParams ( keypair . publicKey . publicKey ) ;
2013-08-16 14:50:47 -04:00
2014-12-15 13:33:02 -05:00
// check if key IDs match
if ( ! keypair . privateKey . _id || keypair . privateKey . _id !== keypair . publicKey . _id || keypair . privateKey . _id !== privKeyParams . _id || keypair . publicKey . _id !== pubKeyParams . _id ) {
throw new Error ( 'Key IDs dont match!' ) ;
2013-10-11 21:19:01 -04:00
}
2014-12-15 13:33:02 -05:00
// check that key userIds contain email address of user account
var matchingPrivUserId = _ . findWhere ( privKeyParams . userIds , {
emailAddress : self . _account . emailAddress
} ) ;
var matchingPubUserId = _ . findWhere ( pubKeyParams . userIds , {
emailAddress : self . _account . emailAddress
} ) ;
2013-12-03 13:21:50 -05:00
2014-12-15 13:33:02 -05:00
if ( ! matchingPrivUserId || ! matchingPubUserId || keypair . privateKey . userId !== self . _account . emailAddress || keypair . publicKey . userId !== self . _account . emailAddress ) {
throw new Error ( 'User IDs dont match!' ) ;
2014-03-05 14:14:23 -05:00
}
2015-02-20 11:55:11 -05:00
2014-12-16 08:12:30 -05:00
resolve ( ) ;
2014-03-05 14:14:23 -05:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
// import existing key pair into crypto module
return self . _pgp . importKeys ( {
passphrase : options . passphrase ,
privateKeyArmored : keypair . privateKey . encryptedKey ,
publicKeyArmored : keypair . publicKey . publicKey
2015-02-20 11:55:11 -05:00
} ) . then ( function ( ) {
return keypair ;
2014-03-05 14:14:23 -05:00
} ) ;
2014-12-15 13:33:02 -05:00
} ) . then ( setPrivateKey ) ;
}
2015-02-20 11:55:11 -05:00
function setPrivateKey ( keypair ) {
2014-12-15 13:33:02 -05:00
// set decrypted privateKey to pgpMailer
self . _pgpbuilder . _privateKey = self . _pgp . _privateKey ;
2015-02-20 11:55:11 -05:00
return keypair ;
2014-10-02 16:05:44 -04:00
}
} ;
/ * *
* Opens a folder in IMAP so that we can receive updates for it .
* Please note that this is a no - op if you try to open the outbox , since it is not an IMAP folder
* but a virtual folder that only exists on disk .
*
* @ param { Object } options . folder The folder to be opened
* /
2014-12-15 13:33:02 -05:00
Email . prototype . openFolder = function ( options ) {
var self = this ;
2015-02-12 10:10:46 -05:00
return new Promise ( function ( resolve ) {
2014-12-15 13:33:02 -05:00
self . checkOnline ( ) ;
2015-02-12 10:10:46 -05:00
resolve ( ) ;
2014-05-23 08:23:50 -04:00
2015-02-12 10:10:46 -05:00
} ) . then ( function ( ) {
if ( options . folder . path !== config . outboxMailboxPath ) {
return self . _imapClient . selectMailbox ( {
path : options . folder . path
} ) ;
2014-12-15 13:33:02 -05:00
}
} ) ;
2014-10-02 16:05:44 -04:00
} ;
/ * *
* Delete a message from IMAP , disk and folder . messages .
*
* Please note that this deletes from disk only if you delete from the outbox ,
* since it is not an IMAP folder but a virtual folder that only exists on disk .
*
* @ param { Object } options . folder The folder from which to delete the messages
* @ param { Object } options . message The message that should be deleted
* @ param { Boolean } options . localOnly Indicated if the message should not be removed from IMAP
2014-12-15 13:33:02 -05:00
* @ return { Promise }
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . deleteMessage = function ( options ) {
2014-10-02 16:05:44 -04:00
var self = this ,
folder = options . folder ,
message = options . message ;
self . busy ( ) ;
folder . messages . splice ( folder . messages . indexOf ( message ) , 1 ) ;
// delete only locally
if ( options . localOnly || options . folder . path === config . outboxMailboxPath ) {
2014-12-15 13:33:02 -05:00
return deleteLocal ( ) . then ( done ) . catch ( done ) ;
2014-10-02 16:05:44 -04:00
}
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve ) {
self . checkOnline ( ) ;
resolve ( ) ;
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
2014-10-02 16:05:44 -04:00
// delete from IMAP
2014-12-15 13:33:02 -05:00
return self . _imapDeleteMessage ( {
2014-10-02 16:05:44 -04:00
folder : folder ,
uid : message . uid
} ) ;
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
return deleteLocal ( ) ;
} ) . then ( done ) . catch ( done ) ;
2014-01-18 05:42:28 -05:00
2014-10-02 16:05:44 -04:00
function deleteLocal ( ) {
// delete from indexed db
2014-12-15 13:33:02 -05:00
return self . _localDeleteMessage ( {
2014-10-02 16:05:44 -04:00
folder : folder ,
uid : message . uid
2014-12-15 13:33:02 -05:00
} ) ;
2014-10-02 16:05:44 -04:00
}
2014-02-24 11:37:23 -05:00
2014-10-02 16:05:44 -04:00
function done ( err ) {
self . done ( ) ; // stop the spinner
2014-12-15 13:33:02 -05:00
updateUnreadCount ( folder ) ; // update the unread count, if necessary
2014-10-02 16:05:44 -04:00
if ( err ) {
folder . messages . unshift ( message ) ; // re-add the message to the folder in case of an error
2014-12-15 13:33:02 -05:00
throw err ;
2014-02-14 11:29:16 -05:00
}
2014-10-02 16:05:44 -04:00
}
} ;
/ * *
* Updates a message 's ' unread ' and ' answered ' flags
*
* Please note if you set flags on disk only if you delete from the outbox ,
* since it is not an IMAP folder but a virtual folder that only exists on disk .
*
2014-12-15 13:33:02 -05:00
* @ param { Object } options . folder The origin folder
* @ param { Object } options . message The message that should change flags
* @ return { Promise }
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . setFlags = function ( options ) {
2014-10-02 16:05:44 -04:00
var self = this ,
folder = options . folder ,
message = options . message ;
// no-op if the message if not present anymore (for whatever reason)
if ( folder . messages . indexOf ( message ) < 0 ) {
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve ) {
resolve ( ) ;
} ) ;
2014-10-02 16:05:44 -04:00
}
2013-08-21 07:43:19 -04:00
2014-12-15 13:33:02 -05:00
self . busy ( ) ; // start the spinner
2014-10-02 16:05:44 -04:00
// don't do a roundtrip to IMAP,
// especially if you want to mark outbox messages
if ( options . localOnly || options . folder . path === config . outboxMailboxPath ) {
2014-12-15 13:33:02 -05:00
return markStorage ( ) . then ( done ) . catch ( done ) ;
2014-10-02 16:05:44 -04:00
}
2014-05-13 07:13:36 -04:00
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve ) {
self . checkOnline ( ) ;
resolve ( ) ;
2014-06-27 10:19:30 -04:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
2014-10-02 16:05:44 -04:00
// mark a message unread/answered on IMAP
2014-12-15 13:33:02 -05:00
return self . _imapMark ( {
2014-10-02 16:05:44 -04:00
folder : folder ,
uid : options . message . uid ,
unread : options . message . unread ,
2014-11-05 08:25:05 -05:00
answered : options . message . answered ,
flagged : options . message . flagged
2014-10-02 16:05:44 -04:00
} ) ;
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
return markStorage ( ) ;
} ) . then ( done ) . catch ( done ) ;
2014-07-04 11:58:25 -04:00
2014-10-02 16:05:44 -04:00
function markStorage ( ) {
// angular pollutes that data transfer objects with helper properties (e.g. $$hashKey),
// which we do not want to persist to disk. in order to avoid that, we load the pristine
// message from disk, change the flags and re-persist it to disk
2014-12-15 13:33:02 -05:00
return self . _localListMessages ( {
2014-10-02 16:05:44 -04:00
folder : folder ,
uid : options . message . uid ,
2014-12-15 13:33:02 -05:00
} ) . then ( function ( storedMessages ) {
2014-10-02 16:05:44 -04:00
// set the flags
var storedMessage = storedMessages [ 0 ] ;
2015-02-12 10:18:42 -05:00
if ( ! storedMessage ) {
// the message has been deleted in the meantime
return ;
}
2014-10-02 16:05:44 -04:00
storedMessage . unread = options . message . unread ;
2014-11-05 08:25:05 -05:00
storedMessage . flagged = options . message . flagged ;
2014-10-02 16:05:44 -04:00
storedMessage . answered = options . message . answered ;
storedMessage . modseq = options . message . modseq || storedMessage . modseq ;
2014-06-25 08:13:46 -04:00
2014-10-02 16:05:44 -04:00
// store
2014-12-15 13:33:02 -05:00
return self . _localStoreMessages ( {
2014-10-02 16:05:44 -04:00
folder : folder ,
emails : [ storedMessage ]
2014-12-15 13:33:02 -05:00
} ) ;
2014-10-02 16:05:44 -04:00
} ) ;
}
2014-07-04 11:58:25 -04:00
2014-10-02 16:05:44 -04:00
function done ( err ) {
self . done ( ) ; // stop the spinner
updateUnreadCount ( folder ) ; // update the unread count
2014-12-15 13:33:02 -05:00
if ( err ) {
throw err ;
}
2014-10-02 16:05:44 -04:00
}
} ;
2014-11-04 14:31:09 -05:00
/ * *
* Moves a message to another folder
2014-11-05 04:55:26 -05:00
*
2014-11-04 14:31:09 -05:00
* @ param { Object } options . folder The origin folder
* @ param { Object } options . destination The destination folder
* @ param { Object } options . message The message that should be moved
2014-12-15 13:33:02 -05:00
* @ return { Promise }
2014-11-04 14:31:09 -05:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . moveMessage = function ( options ) {
2014-11-04 14:31:09 -05:00
var self = this ,
folder = options . folder ,
destination = options . destination ,
message = options . message ;
self . busy ( ) ;
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve ) {
self . checkOnline ( ) ;
resolve ( ) ;
2014-11-04 14:31:09 -05:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
folder . messages . splice ( folder . messages . indexOf ( message ) , 1 ) ;
2014-11-04 14:31:09 -05:00
2014-12-15 13:33:02 -05:00
// delete from IMAP
return self . _imapMoveMessage ( {
folder : folder ,
destination : destination ,
uid : message . uid
} ) . catch ( function ( err ) {
2014-11-04 14:31:09 -05:00
// re-add the message to the folder in case of an error, only makes sense if IMAP errors
folder . messages . unshift ( message ) ;
done ( err ) ;
2014-12-15 13:33:02 -05:00
} ) ;
2014-11-04 14:31:09 -05:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
2014-11-04 14:31:09 -05:00
// delete from local indexed db, will be synced when new folder is opened
2014-12-15 13:33:02 -05:00
return self . _localDeleteMessage ( {
2014-11-04 14:31:09 -05:00
folder : folder ,
uid : message . uid
2014-12-15 13:33:02 -05:00
} ) ;
} ) . then ( done ) . catch ( done ) ;
2014-11-04 14:31:09 -05:00
function done ( err ) {
self . done ( ) ; // stop the spinner
updateUnreadCount ( folder ) ; // update the unread count, if necessary
2014-12-15 13:33:02 -05:00
if ( err ) {
throw err ;
}
2014-11-04 14:31:09 -05:00
}
} ;
2014-10-02 16:05:44 -04:00
/ * *
* Streams message content
* @ param { Object } options . message The message for which to retrieve the body
* @ param { Object } options . folder The IMAP folder
2014-12-15 13:33:02 -05:00
* @ return { Promise }
* @ resolve { Object } The message object that was streamed
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . getBody = function ( options ) {
2014-10-02 16:05:44 -04:00
var self = this ,
2015-03-31 09:43:42 -04:00
messages = options . messages ,
folder = options . folder ;
messages = messages . filter ( function ( message ) {
// the message either already has a body or is fetching it right now, so no need to become active here
return ! ( message . loadingBody || typeof message . body !== 'undefined' ) ;
} ) ;
2014-10-02 16:05:44 -04:00
2015-03-31 09:43:42 -04:00
if ( ! messages . length ) {
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve ) {
resolve ( ) ;
} ) ;
2014-10-02 16:05:44 -04:00
}
2014-07-04 11:58:25 -04:00
2015-03-31 09:43:42 -04:00
messages . forEach ( function ( message ) {
message . loadingBody = true ;
} ) ;
2014-10-02 16:05:44 -04:00
self . busy ( ) ;
2014-07-04 11:58:25 -04:00
2015-03-31 09:43:42 -04:00
var loadedMessages ;
// load the message from disk
2014-12-15 13:33:02 -05:00
return self . _localListMessages ( {
folder : folder ,
2015-03-31 09:43:42 -04:00
uid : _ . pluck ( messages , MSG _ATTR _UID )
2014-12-15 13:33:02 -05:00
} ) . then ( function ( localMessages ) {
2015-03-31 09:43:42 -04:00
loadedMessages = localMessages ;
2015-02-13 11:10:44 -05:00
2015-03-31 09:43:42 -04:00
// find out which messages are not available on disk (uids not included in disk roundtrip)
var localUids = _ . pluck ( localMessages , MSG _ATTR _UID ) ;
var needsImapFetch = messages . filter ( function ( msg ) {
return ! _ . contains ( localUids , msg . uid ) ;
2014-12-15 13:33:02 -05:00
} ) ;
2015-03-31 09:43:42 -04:00
return needsImapFetch ;
2014-02-14 11:29:16 -05:00
2015-03-31 09:43:42 -04:00
} ) . then ( function ( needsImapFetch ) {
// get the missing messages from imap
2013-08-20 07:30:35 -04:00
2015-03-31 09:43:42 -04:00
if ( ! needsImapFetch . length ) {
// no imap roundtrip needed, we're done
return loadedMessages ;
2014-05-12 07:44:02 -04:00
}
2015-03-31 09:43:42 -04:00
// do the imap roundtrip
return self . _fetchMessages ( {
messages : needsImapFetch ,
folder : folder
} ) . then ( function ( imapMessages ) {
// add the messages from imap to the loaded messages
loadedMessages = loadedMessages . concat ( imapMessages ) ;
2014-01-16 09:37:08 -05:00
2015-03-31 09:43:42 -04:00
} ) . catch ( function ( err ) {
axe . error ( 'Can not fetch messages from IMAP. Reason: ' + err . message + ( err . stack ? ( '\n' + err . stack ) : '' ) ) ;
2013-12-09 13:21:52 -05:00
2015-03-31 09:43:42 -04:00
// stop the loading spinner for those messages we can't fetch
needsImapFetch . forEach ( function ( message ) {
message . loadingBody = false ;
} ) ;
2013-10-04 10:29:32 -04:00
2015-03-31 09:43:42 -04:00
// we can't fetch from imap, just continue with what we have
messages = _ . difference ( messages , needsImapFetch ) ;
} ) ;
2014-10-02 16:05:44 -04:00
2015-03-31 09:43:42 -04:00
} ) . then ( function ( ) {
// enhance dummy messages with content
messages . forEach ( function ( message ) {
var loadedMessage = _ . findWhere ( loadedMessages , {
uid : message . uid
} ) ;
2014-07-11 09:03:03 -04:00
2015-03-31 09:43:42 -04:00
// enhance the dummy message with the loaded content
_ . extend ( message , loadedMessage ) ;
} ) ;
2014-07-29 08:13:00 -04:00
2015-03-31 09:43:42 -04:00
} ) . then ( function ( ) {
// extract the message body
var jobs = [ ] ;
2014-07-29 08:13:00 -04:00
2015-03-31 09:43:42 -04:00
messages . forEach ( function ( message ) {
var job = self . _extractBody ( message ) . catch ( function ( err ) {
axe . error ( 'Can extract body for message uid ' + message . uid + ' . Reason: ' + err . message + ( err . stack ? ( '\n' + err . stack ) : '' ) ) ;
} ) ;
jobs . push ( job ) ;
2014-10-02 16:05:44 -04:00
} ) ;
2014-07-01 13:49:19 -04:00
2015-03-31 09:43:42 -04:00
return Promise . all ( jobs ) ;
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
2015-03-31 09:43:42 -04:00
done ( ) ;
2014-07-11 09:22:34 -04:00
2015-03-31 09:43:42 -04:00
if ( options . notifyNew && messages . length ) {
// notify for incoming mail
self . onIncomingMessage ( messages ) ;
2015-02-13 11:10:44 -05:00
}
2015-03-31 09:43:42 -04:00
return messages ;
} ) . catch ( function ( err ) {
done ( ) ;
2014-12-15 13:33:02 -05:00
throw err ;
} ) ;
2015-03-31 09:43:42 -04:00
function done ( ) {
messages . forEach ( function ( message ) {
message . loadingBody = false ;
} ) ;
self . done ( ) ;
2014-10-02 16:05:44 -04:00
}
} ;
2014-07-11 09:22:34 -04:00
2014-12-15 13:33:02 -05:00
Email . prototype . _checkSignatures = function ( message ) {
2014-10-02 16:05:44 -04:00
var self = this ;
2014-12-15 13:33:02 -05:00
return self . _keychain . getReceiverPublicKey ( message . from [ 0 ] . address ) . then ( function ( senderPublicKey ) {
2014-10-02 16:05:44 -04:00
// get the receiver's public key to check the message signature
var senderKey = senderPublicKey ? senderPublicKey . publicKey : undefined ;
if ( message . clearSignedMessage ) {
2014-12-15 13:33:02 -05:00
return self . _pgp . verifyClearSignedMessage ( message . clearSignedMessage , senderKey ) ;
2014-10-02 16:05:44 -04:00
} else if ( message . signedMessage && message . signature ) {
2014-12-15 13:33:02 -05:00
return self . _pgp . verifySignedMessage ( message . signedMessage , message . signature , senderKey ) ;
2014-10-02 16:05:44 -04:00
}
} ) ;
} ;
/ * *
* Retrieves an attachment matching a body part for a given uid and a folder
*
* @ param { Object } options . folder The folder where to find the attachment
* @ param { Number } options . uid The uid for the message the attachment body part belongs to
* @ param { Object } options . attachment The attachment body part to fetch and parse from IMAP
2014-12-15 13:33:02 -05:00
* @ return { Promise }
* @ resolve { Object } attachment The attachment body part that was retrieved and parsed
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . getAttachment = function ( options ) {
2014-10-02 16:05:44 -04:00
var self = this ,
attachment = options . attachment ;
attachment . busy = true ;
2014-12-15 13:33:02 -05:00
return self . _getBodyParts ( {
2014-10-02 16:05:44 -04:00
folder : options . folder ,
uid : options . uid ,
bodyParts : [ attachment ]
2014-12-15 13:33:02 -05:00
} ) . then ( function ( parsedBodyParts ) {
2014-10-02 16:05:44 -04:00
attachment . busy = false ;
// add the content to the original object
attachment . content = parsedBodyParts [ 0 ] . content ;
2014-12-15 13:33:02 -05:00
return attachment ;
} ) . catch ( function ( err ) {
attachment . busy = false ;
throw err ;
2014-10-02 16:05:44 -04:00
} ) ;
} ;
/ * *
* Decrypts a message and replaces sets the decrypted plaintext as the message ' s body , html , or attachment , respectively .
* The first encrypted body part ' s ciphertext ( in the content property ) will be decrypted .
*
* @ param { Object } options . message The message
2014-12-15 13:33:02 -05:00
* @ return { Promise }
* @ resolve { Object } message The decrypted message object
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . decryptBody = function ( options ) {
2014-10-02 16:05:44 -04:00
var self = this ,
2014-12-15 13:33:02 -05:00
message = options . message ,
encryptedNode ;
2014-10-02 16:05:44 -04:00
// the message is decrypting has no body, is not encrypted or has already been decrypted
if ( ! message . bodyParts || message . decryptingBody || ! message . body || ! message . encrypted || message . decrypted ) {
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve ) {
resolve ( message ) ;
} ) ;
2014-10-02 16:05:44 -04:00
}
2014-05-23 08:23:50 -04:00
2014-10-02 16:05:44 -04:00
message . decryptingBody = true ;
self . busy ( ) ;
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
// get the sender's public key for signature checking
return self . _keychain . getReceiverPublicKey ( message . from [ 0 ] . address ) . then ( function ( senderPublicKey ) {
2014-10-02 16:05:44 -04:00
// get the receiver's public key to check the message signature
2014-12-15 13:33:02 -05:00
encryptedNode = filterBodyParts ( message . bodyParts , MSG _PART _TYPE _ENCRYPTED ) [ 0 ] ;
2014-10-02 16:05:44 -04:00
var senderKey = senderPublicKey ? senderPublicKey . publicKey : undefined ;
2014-12-15 13:33:02 -05:00
return self . _pgp . decrypt ( encryptedNode . content , senderKey ) ;
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( pt ) {
if ( ! pt . decrypted ) {
2014-12-16 08:12:30 -05:00
throw new Error ( 'Error decrypting message.' ) ;
2014-12-15 13:33:02 -05:00
}
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
// if the decryption worked and signatures are present, everything's fine.
// no error is thrown if signatures are not present
message . signed = typeof pt . signaturesValid !== 'undefined' ;
message . signaturesValid = pt . signaturesValid ;
2014-10-02 16:05:44 -04:00
2014-12-15 13:33:02 -05:00
// if the encrypted node contains pgp/inline, we must not parse it
// with the mailreader as it is not well-formed MIME
if ( encryptedNode . _isPgpInline ) {
message . body = pt . decrypted ;
message . decrypted = true ;
return ;
}
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
// the mailparser works on the .raw property
encryptedNode . raw = pt . decrypted ;
// parse the decrypted raw content in the mailparser
return self . _parse ( {
bodyParts : [ encryptedNode ]
2014-12-16 08:12:30 -05:00
} ) . then ( handleRaw ) ;
} ) . then ( function ( ) {
self . done ( ) ; // stop the spinner
message . decryptingBody = false ;
return message ;
} ) . catch ( function ( err ) {
self . done ( ) ; // stop the spinner
message . decryptingBody = false ;
message . body = err . message ; // display error msg in body
message . decrypted = true ;
return message ;
} ) ;
2014-05-23 08:23:50 -04:00
2014-12-16 08:12:30 -05:00
function handleRaw ( root ) {
2014-12-15 13:33:02 -05:00
if ( message . signed ) {
// message had a signature in the ciphertext, so we're done here
return setBody ( root ) ;
}
2014-06-23 04:59:17 -04:00
2014-12-15 13:33:02 -05:00
// message had no signature in the ciphertext, so there's a little extra effort to be done here
// is there a signed MIME node?
var signedRoot = filterBodyParts ( root , MSG _PART _TYPE _SIGNED ) [ 0 ] ;
if ( ! signedRoot ) {
// no signed MIME node, obviously an unsigned PGP/MIME message
return setBody ( root ) ;
}
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
// if there is something signed in here, we're only interested in the signed content
message . signedMessage = signedRoot . signedMessage ;
message . signature = signedRoot . signature ;
root = signedRoot . content ;
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
// check the signatures for encrypted messages
return self . _checkSignatures ( message ) . then ( function ( signaturesValid ) {
message . signed = typeof signaturesValid !== 'undefined' ;
message . signaturesValid = signaturesValid ;
2014-12-16 08:12:30 -05:00
return setBody ( root ) ;
2014-05-23 08:23:50 -04:00
} ) ;
2014-12-16 08:12:30 -05:00
}
2014-12-15 13:33:02 -05:00
function setBody ( root ) {
// we have successfully interpreted the descrypted message,
// so let's update the views on the message parts
message . body = _ . pluck ( filterBodyParts ( root , MSG _PART _TYPE _TEXT ) , MSG _PART _ATTR _CONTENT ) . join ( '\n' ) ;
message . html = _ . pluck ( filterBodyParts ( root , MSG _PART _TYPE _HTML ) , MSG _PART _ATTR _CONTENT ) . join ( '\n' ) ;
message . attachments = _ . reject ( filterBodyParts ( root , MSG _PART _TYPE _ATTACHMENT ) , function ( attmt ) {
// remove the pgp-signature from the attachments
return attmt . mimeType === "application/pgp-signature" ;
} ) ;
inlineExternalImages ( message ) ;
message . decrypted = true ;
2014-12-16 08:12:30 -05:00
return message ;
2014-10-02 16:05:44 -04:00
}
} ;
/ * *
* Encrypted ( if necessary ) and sends a message with a predefined clear text greeting .
*
* @ param { Object } options . email The message to be sent
2015-01-22 06:07:14 -05:00
* @ param { Object } mailer an instance of the pgpmailer to be used for testing purposes only
2014-10-02 16:05:44 -04:00
* /
2015-01-22 06:07:14 -05:00
Email . prototype . sendEncrypted = function ( options , mailer ) {
2014-12-16 08:12:30 -05:00
// mime encode, sign, encrypt and send email via smtp
return this . _sendGeneric ( {
encrypt : true ,
smtpclient : options . smtpclient , // filled solely in the integration test, undefined in normal usage
mail : options . email ,
publicKeysArmored : options . email . publicKeysArmored
2015-01-22 06:07:14 -05:00
} , mailer ) ;
2014-10-02 16:05:44 -04:00
} ;
/ * *
* Sends a signed message in the plain
*
* @ param { Object } options . email The message to be sent
2015-01-22 06:07:14 -05:00
* @ param { Object } mailer an instance of the pgpmailer to be used for testing purposes only
2014-10-02 16:05:44 -04:00
* /
2015-01-22 06:07:14 -05:00
Email . prototype . sendPlaintext = function ( options , mailer ) {
2014-12-16 08:12:30 -05:00
// add suffix to plaintext mail
2015-02-16 17:16:18 -05:00
options . email . body += str . signature + config . keyServerUrl + '/' + this . _account . emailAddress ;
2014-12-16 08:12:30 -05:00
// mime encode, sign and send email via smtp
return this . _sendGeneric ( {
smtpclient : options . smtpclient , // filled solely in the integration test, undefined in normal usage
mail : options . email
2015-01-22 06:07:14 -05:00
} , mailer ) ;
2014-12-16 08:12:30 -05:00
} ;
/ * *
* This funtion wraps error handling for sending via pgpMailer and uploading to imap .
* @ param { Object } options . email The message to be sent
2015-01-22 06:07:14 -05:00
* @ param { Object } mailer an instance of the pgpmailer to be used for testing purposes only
2014-12-16 08:12:30 -05:00
* /
2015-01-22 06:07:14 -05:00
Email . prototype . _sendGeneric = function ( options , mailer ) {
2014-10-02 16:05:44 -04:00
var self = this ;
self . busy ( ) ;
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve ) {
self . checkOnline ( ) ;
resolve ( ) ;
} ) . then ( function ( ) {
2015-01-22 06:07:14 -05:00
// get the smtp credentials
return self . _auth . getCredentials ( ) ;
} ) . then ( function ( credentials ) {
// gmail does not require you to upload to the sent items folder after successful sending, whereas most other providers do
self . ignoreUploadOnSent = self . checkIgnoreUploadOnSent ( credentials . smtp . host ) ;
// tls socket worker path for multithreaded tls in non-native tls environments
credentials . smtp . tlsWorkerPath = config . workerPath + '/tcp-socket-tls-worker.min.js' ;
// create a new pgpmailer
self . _pgpMailer = ( mailer || new PgpMailer ( credentials . smtp , self . _pgpbuilder ) ) ;
// certificate update retriggers sending after cert update is persisted
self . _pgpMailer . onCert = self . _auth . handleCertificateUpdate . bind ( self . _auth , 'smtp' , self . _sendGeneric . bind ( self , options ) , self . _dialog . error ) ;
} ) . then ( function ( ) {
2014-10-02 16:05:44 -04:00
2015-01-22 06:07:14 -05:00
// send the email
2015-02-12 10:10:46 -05:00
return self . _pgpMailer . send ( options ) ;
2014-12-15 13:33:02 -05:00
} ) . then ( function ( rfcText ) {
2014-10-22 06:25:27 -04:00
// try to upload to sent, but we don't actually care if the upload failed or not
// this should not negatively impact the process of sending
2014-12-15 13:33:02 -05:00
return self . _uploadToSent ( {
2014-10-02 16:05:44 -04:00
message : rfcText
2014-12-16 08:12:30 -05:00
} ) . catch ( function ( ) { } ) ;
2014-12-15 13:33:02 -05:00
} ) . then ( done ) . catch ( done ) ;
function done ( err ) {
self . done ( ) ; // stop the spinner
if ( err ) {
throw err ;
}
}
2014-10-02 16:05:44 -04:00
} ;
/ * *
* Signs and encrypts a message
*
* @ param { Object } options . email The message to be encrypted
2015-02-12 10:10:46 -05:00
* @ param { Function } callback ( message ) Invoked when the message was encrypted , or an error occurred
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . encrypt = function ( options ) {
2014-10-02 16:05:44 -04:00
var self = this ;
self . busy ( ) ;
2015-02-12 10:10:46 -05:00
return self . _pgpbuilder . encrypt ( options ) . then ( function ( message ) {
self . done ( ) ;
return message ;
2014-10-02 16:05:44 -04:00
} ) ;
} ;
2015-03-31 09:43:42 -04:00
/ * *
* Synchronizes the outbox ' s contents from disk to memory .
* If a message has disappeared from the disk , this method will remove
* it from folder . messages , and it adds any messages from disk to memory the are not yet in folder . messages
*
* @ param { Object } options . folder The folder to synchronize
* /
Email . prototype . refreshOutbox = function ( ) {
var outbox = _ . findWhere ( this . _account . folders , {
type : config . outboxMailboxType
} ) ;
return this . _localListMessages ( {
folder : outbox ,
exactmatch : false
} ) . then ( function ( storedMessages ) {
var storedUids = _ . pluck ( storedMessages , MSG _ATTR _UID ) ,
memoryUids = _ . pluck ( outbox . messages , MSG _ATTR _UID ) ,
newUids = _ . difference ( storedUids , memoryUids ) , // uids of messages that are not yet in memory
removedUids = _ . difference ( memoryUids , storedUids ) ; // uids of messages that are no longer stored on the disk
// add new messages that are not yet in memory
_ . filter ( storedMessages , function ( msg ) {
return _ . contains ( newUids , msg . uid ) ;
} ) . forEach ( function ( newMessage ) {
outbox . messages . push ( newMessage ) ;
} ) ;
// remove messages that are no longer on disk, i.e. have been removed/sent/...
_ . filter ( outbox . messages , function ( msg ) {
return _ . contains ( removedUids , msg . uid ) ;
} ) . forEach ( function ( removedMessage ) {
var index = outbox . messages . indexOf ( removedMessage ) ;
outbox . messages . splice ( index , 1 ) ;
} ) ;
2015-04-27 12:15:36 -04:00
updateUnreadCount ( outbox , true ) ; // update the unread count, count all messages
2015-03-31 09:43:42 -04:00
} ) ;
} ;
2014-10-02 16:05:44 -04:00
//
//
// Event Handlers
//
//
/ * *
* This handler should be invoked when navigator . onLine === true . It will try to connect a
* given instance of the imap client . If the connection attempt was successful , it will
* update the locally available folders with the newly received IMAP folder listing .
*
2015-01-22 06:07:14 -05:00
* @ param { Object } imap an instance of the imap - client to be used for testing purposes only
2014-10-02 16:05:44 -04:00
* /
2015-01-22 06:07:14 -05:00
Email . prototype . onConnect = function ( imap ) {
2014-10-02 16:05:44 -04:00
var self = this ;
2015-03-04 08:16:44 -05:00
if ( ! self . isOnline ( ) ) {
// don't try to connect when navigator is offline
return new Promise ( function ( resolve ) {
resolve ( ) ;
} ) ;
}
2014-10-02 16:05:44 -04:00
self . _account . loggingIn = true ;
2015-01-22 06:07:14 -05:00
// init imap/smtp clients
return self . _auth . getCredentials ( ) . then ( function ( credentials ) {
// add the maximum update batch size for imap folders to the imap configuration
credentials . imap . maxUpdateSize = config . imapUpdateBatchSize ;
// tls socket worker path for multithreaded tls in non-native tls environments
credentials . imap . tlsWorkerPath = config . workerPath + '/tcp-socket-tls-worker.min.js' ;
2015-04-12 17:43:13 -04:00
// enable multithreaded compression handling
credentials . imap . compressionWorkerPath = config . workerPath + '/browserbox-compression-worker.min.js' ;
2014-10-02 16:05:44 -04:00
2015-01-22 06:07:14 -05:00
self . _imapClient = ( imap || new ImapClient ( credentials . imap ) ) ;
2014-11-12 10:41:40 -05:00
2015-01-22 06:07:14 -05:00
self . _imapClient . onError = onConnectionError ; // connection error handling
self . _imapClient . onCert = self . _auth . handleCertificateUpdate . bind ( self . _auth , 'imap' , self . onConnect . bind ( self ) , self . _dialog . error ) ; // certificate update handling
self . _imapClient . onSyncUpdate = self . _onSyncUpdate . bind ( self ) ; // attach sync update handler
} ) . then ( function ( ) {
// imap login
2015-02-12 10:10:46 -05:00
return self . _imapClient . login ( ) ;
2015-01-22 06:07:14 -05:00
} ) . then ( function ( ) {
2014-10-02 16:05:44 -04:00
self . _account . loggingIn = false ;
// init folders
2015-03-31 09:43:42 -04:00
return self . _updateFolders ( ) ;
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
// fill the imap mailboxCache with information we have locally available:
// - highest locally available moseq (NB! JavaScript can't handle 64 bit uints, so modseq values are strings)
// - list of locally available uids
// - highest locally available uid
// - next expected uid
var mailboxCache = { } ;
self . _account . folders . forEach ( function ( folder ) {
2015-03-31 09:43:42 -04:00
var uids = folder . uids . sort ( function ( a , b ) {
2014-12-15 13:33:02 -05:00
return a - b ;
2014-10-02 16:05:44 -04:00
} ) ;
2015-03-31 09:43:42 -04:00
var lastUid = uids [ uids . length - 1 ] ;
2014-12-15 13:33:02 -05:00
mailboxCache [ folder . path ] = {
exists : lastUid ,
uidNext : lastUid + 1 ,
uidlist : uids ,
2015-03-31 09:43:42 -04:00
highestModseq : '' + folder . modseq
2014-12-15 13:33:02 -05:00
} ;
} ) ;
self . _imapClient . mailboxCache = mailboxCache ;
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
// set status to online after setting cache to prevent race condition
self . _account . online = true ;
2014-07-31 03:57:57 -04:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
// by default, select the inbox (if there is one) after connecting the imap client.
// this avoids race conditions between the listening imap connection and the one where the work is done
var inbox = _ . findWhere ( self . _account . folders , {
type : FOLDER _TYPE _INBOX
} ) ;
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
if ( ! inbox ) {
// if there is no inbox, that's ok, too
return ;
}
return self . openFolder ( {
folder : inbox
} ) . then ( function ( ) {
// set up the imap client to listen for changes in the inbox
self . _imapClient . listenForChanges ( {
path : inbox . path
} , function ( ) { } ) ;
} ) ;
} ) ;
2013-12-03 13:21:50 -05:00
2015-01-22 06:07:14 -05:00
function onConnectionError ( error ) {
axe . debug ( 'IMAP connection error, disconnected. Reason: ' + error . message + ( error . stack ? ( '\n' + error . stack ) : '' ) ) ;
if ( ! self . isOnline ( ) ) {
return ;
}
axe . debug ( 'Attempting reconnect in ' + config . reconnectInterval / 1000 + ' seconds.' ) ;
setTimeout ( function ( ) {
axe . debug ( 'Reconnecting the IMAP stack' ) ;
// re-init client modules on error
self . onConnect ( ) . catch ( self . _dialog . error ) ;
} , config . reconnectInterval ) ;
2014-12-15 13:33:02 -05:00
}
2014-10-02 16:05:44 -04:00
} ;
/ * *
* This handler should be invoked when navigator . onLine === false .
* It will discard the imap client and pgp mailer
* /
2014-12-15 13:33:02 -05:00
Email . prototype . onDisconnect = function ( ) {
2014-10-02 16:05:44 -04:00
// logout of imap-client
2014-12-01 04:45:30 -05:00
// ignore error, because it's not problem if logout fails
2015-05-19 10:21:52 -04:00
if ( this . _imapClient ) {
this . _imapClient . stopListeningForChanges ( function ( ) { } ) ;
this . _imapClient . logout ( function ( ) { } ) ;
}
2014-10-02 16:05:44 -04:00
// discard clients
2014-12-01 04:45:30 -05:00
this . _account . online = false ;
this . _imapClient = undefined ;
this . _pgpMailer = undefined ;
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve ) {
resolve ( ) ; // ASYNC ALL THE THINGS!!!
} ) ;
2014-10-02 16:05:44 -04:00
} ;
/ * *
* The are updates in the IMAP folder of the following type
* - 'new' : a list of uids that are newly available
* - 'deleted' : a list of uids that were deleted from IMAP available
* - 'messages' : a list of messages ( uid + flags ) that where changes are available
*
* @ param { String } options . type The type of the update
* @ param { String } options . path The mailbox for which updates are available
* @ param { Array } options . list Array containing update information . Number ( uid ) or mail with Object ( uid and flags ) , respectively
* /
2014-11-18 12:44:00 -05:00
Email . prototype . _onSyncUpdate = function ( options ) {
2015-03-31 09:43:42 -04:00
var self = this ,
uids = options . list ;
2014-10-02 16:05:44 -04:00
var folder = _ . findWhere ( self . _account . folders , {
path : options . path
} ) ;
if ( ! folder ) {
2015-03-31 09:43:42 -04:00
return ; // ignore updates for an unknown folder
2014-10-02 16:05:44 -04:00
}
2014-05-23 08:23:50 -04:00
2014-10-02 16:05:44 -04:00
if ( options . type === SYNC _TYPE _NEW ) {
2015-03-31 09:43:42 -04:00
// new messages available on imap, add the new uids to the folder
uids = _ . difference ( uids , folder . uids ) ; // eliminate duplicates
var maxUid = folder . uids . length ? Math . max . apply ( null , folder . uids ) : 0 ; // find highest uid prior to update
// add to folder's uids, persist folder
Array . prototype . push . apply ( folder . uids , uids ) ;
self . _localStoreFolders ( ) ;
// add dummy messages to the message list
Array . prototype . push . apply ( folder . messages , uids . map ( function ( uid ) {
return {
uid : uid
} ;
} ) ) ;
if ( maxUid ) {
// folder not empty, find and download the 20 newest bodies. Notify for the inbox
var fetch = _ . filter ( folder . messages , function ( msg ) {
return msg . uid > maxUid ;
} ) . sort ( function ( a , b ) {
return a . uid - b . uid ;
} ) . slice ( - 20 ) ;
self . getBody ( {
folder : folder ,
messages : fetch ,
notifyNew : folder . type === FOLDER _TYPE _INBOX
} ) . catch ( self . _dialog . error ) ;
}
2014-10-02 16:05:44 -04:00
} else if ( options . type === SYNC _TYPE _DELETED ) {
2015-03-31 09:43:42 -04:00
// messages have been deleted
folder . uids = _ . difference ( folder . uids , uids ) ; // remove the uids from the uid list
uids . forEach ( function ( uid ) {
2014-10-02 16:05:44 -04:00
var message = _ . findWhere ( folder . messages , {
uid : uid
} ) ;
2014-06-23 07:03:56 -04:00
2014-10-02 16:05:44 -04:00
if ( ! message ) {
return ;
}
self . deleteMessage ( {
folder : folder ,
message : message ,
localOnly : true
2015-03-31 09:43:42 -04:00
} ) . catch ( self . _dialog . error ) ;
2014-10-02 16:05:44 -04:00
} ) ;
} else if ( options . type === SYNC _TYPE _MSGS ) {
// NB! several possible reasons why this could be called.
// if a message in the array has uid value and flag array, it had a possible flag update
2015-03-31 09:43:42 -04:00
uids . forEach ( function ( changedMsg ) {
2014-10-02 16:05:44 -04:00
if ( ! changedMsg . uid || ! changedMsg . flags ) {
return ;
}
2014-06-23 07:03:56 -04:00
2014-10-02 16:05:44 -04:00
var message = _ . findWhere ( folder . messages , {
uid : changedMsg . uid
} ) ;
2014-06-23 07:03:56 -04:00
2015-03-31 09:43:42 -04:00
if ( ! message || ! message . bodyParts ) {
2014-10-02 16:05:44 -04:00
return ;
2014-06-23 07:03:56 -04:00
}
2014-10-02 16:05:44 -04:00
// update unread, answered, modseq to the latest info
message . answered = changedMsg . flags . indexOf ( '\\Answered' ) > - 1 ;
message . unread = changedMsg . flags . indexOf ( '\\Seen' ) === - 1 ;
message . modseq = changedMsg . modseq ;
2014-06-23 07:03:56 -04:00
2014-10-02 16:05:44 -04:00
self . setFlags ( {
folder : folder ,
message : message ,
localOnly : true
2015-03-31 09:43:42 -04:00
} ) . then ( function ( ) {
// update the folder's last known modseq if necessary
var modseq = parseInt ( changedMsg . modseq , 10 ) ;
if ( modseq > folder . modseq ) {
folder . modseq = modseq ;
return self . _localStoreFolders ( ) ;
}
} ) . catch ( self . _dialog . error ) ;
2014-06-23 07:03:56 -04:00
} ) ;
2014-10-02 16:05:44 -04:00
}
} ;
2014-06-23 07:03:56 -04:00
2014-07-11 09:03:03 -04:00
2014-10-02 16:05:44 -04:00
//
//
// Internal API
//
//
2014-07-11 09:03:03 -04:00
2014-10-02 16:05:44 -04:00
/ * *
* Updates the folder information from imap ( if we ' re online ) . Adds / removes folders in account . folders ,
* if we added / removed folder in IMAP . If we have an uninitialized folder that lacks folder . messages ,
* all the locally available messages are loaded from memory .
* /
2015-03-31 09:43:42 -04:00
Email . prototype . _updateFolders = function ( ) {
2014-10-02 16:05:44 -04:00
var self = this ;
self . busy ( ) ; // start the spinner
// fetch list from imap server
2015-02-12 10:10:46 -05:00
return self . _imapClient . listWellKnownFolders ( ) . then ( function ( wellKnownFolders ) {
2014-10-16 06:23:49 -04:00
var foldersChanged = false , // indicates if we need to persist anything to disk
imapFolders = [ ] ; // aggregate all the imap folders
2014-10-02 16:05:44 -04:00
// initialize the folders to something meaningful if that hasn't already happened
self . _account . folders = self . _account . folders || [ ] ;
2014-05-23 08:23:50 -04:00
2014-10-16 06:23:49 -04:00
// smuggle the outbox into the well known folders, which is obv not present on imap
2014-10-02 16:05:44 -04:00
wellKnownFolders [ config . outboxMailboxType ] = [ {
name : config . outboxMailboxName ,
type : config . outboxMailboxType ,
path : config . outboxMailboxPath
} ] ;
2014-05-23 08:23:50 -04:00
2014-10-16 06:23:49 -04:00
// aggregate all of the imap folders in one place
for ( var folderType in wellKnownFolders ) {
if ( wellKnownFolders . hasOwnProperty ( folderType ) && Array . isArray ( wellKnownFolders [ folderType ] ) ) {
imapFolders = imapFolders . concat ( wellKnownFolders [ folderType ] ) ;
}
}
2013-10-04 10:29:32 -04:00
2014-10-16 06:23:49 -04:00
// find out all the imap paths that are new/removed
var imapFolderPaths = _ . pluck ( imapFolders , 'path' ) ,
localFolderPaths = _ . pluck ( self . _account . folders , 'path' ) ,
newFolderPaths = _ . difference ( imapFolderPaths , localFolderPaths ) ,
removedFolderPaths = _ . difference ( localFolderPaths , imapFolderPaths ) ;
2014-05-23 08:23:50 -04:00
2014-10-16 06:23:49 -04:00
// folders need updating if there are new/removed folders
foldersChanged = ! ! newFolderPaths . length || ! ! removedFolderPaths . length ;
2013-12-09 13:21:52 -05:00
2014-10-16 06:23:49 -04:00
// remove all the remotely deleted folders
removedFolderPaths . forEach ( function ( removedPath ) {
self . _account . folders . splice ( self . _account . folders . indexOf ( _ . findWhere ( self . _account . folders , {
path : removedPath
} ) ) , 1 ) ;
} ) ;
2013-10-04 10:29:32 -04:00
2014-10-16 06:23:49 -04:00
// add all the new imap folders
newFolderPaths . forEach ( function ( newPath ) {
self . _account . folders . push ( _ . findWhere ( imapFolders , {
path : newPath
} ) ) ;
} ) ;
2014-05-23 08:23:50 -04:00
2014-11-13 05:55:12 -05:00
//
2014-10-16 06:23:49 -04:00
// by now, all the folders are up to date. now we need to find all the well known folders
2014-11-13 05:55:12 -05:00
//
2014-10-16 06:23:49 -04:00
// check for the well known folders to be displayed in the uppermost ui part
2014-11-05 04:55:26 -05:00
// in that order
var wellknownTypes = [
2014-10-16 06:23:49 -04:00
FOLDER _TYPE _INBOX ,
FOLDER _TYPE _SENT ,
config . outboxMailboxType ,
FOLDER _TYPE _DRAFTS ,
2014-11-05 05:36:42 -05:00
FOLDER _TYPE _TRASH ,
FOLDER _TYPE _FLAGGED
2014-11-05 04:55:26 -05:00
] ;
// make sure the well known folders are detected
wellknownTypes . forEach ( function ( mbxType ) {
2014-10-16 06:23:49 -04:00
// check if there is a well known folder of this type
var wellknownFolder = _ . findWhere ( self . _account . folders , {
type : mbxType ,
wellknown : true
} ) ;
2013-12-03 13:21:50 -05:00
2014-10-16 06:23:49 -04:00
if ( wellknownFolder ) {
// well known folder found, no need to find a replacement
return ;
2014-10-02 16:05:44 -04:00
}
2014-11-13 05:55:12 -05:00
// we have no folder of the respective type marked as wellknown, so find the
2014-10-16 06:23:49 -04:00
// next best folder of the respective type and flag it as wellknown so that
// we can display it properly
wellknownFolder = _ . findWhere ( self . _account . folders , {
type : mbxType
} ) ;
if ( ! wellknownFolder ) {
// no folder of that type, to mark as well known, nothing to do here
2013-12-03 13:21:50 -05:00
return ;
}
2014-10-02 16:05:44 -04:00
2014-10-16 06:23:49 -04:00
wellknownFolder . wellknown = true ;
2014-10-02 16:05:44 -04:00
foldersChanged = true ;
2013-12-03 13:21:50 -05:00
} ) ;
2014-11-05 04:55:26 -05:00
// order folders
self . _account . folders . sort ( function ( a , b ) {
if ( a . wellknown && b . wellknown ) {
// well known folders should be ordered like the types in the wellknownTypes array
return wellknownTypes . indexOf ( a . type ) - wellknownTypes . indexOf ( b . type ) ;
} else if ( a . wellknown && ! b . wellknown ) {
// wellknown folders should always appear BEFORE the other folders
return - 1 ;
} else if ( ! a . wellknown && b . wellknown ) {
// non-wellknown folders should always appear AFTER wellknown folders
return 1 ;
} else {
// non-wellknown folders should be sorted case-insensitive
return a . name . toLowerCase ( ) . localeCompare ( b . name . toLowerCase ( ) ) ;
}
} ) ;
2014-10-02 16:05:44 -04:00
// if folders have not changed, can fill them with messages directly
2015-03-31 09:43:42 -04:00
if ( foldersChanged ) {
return self . _localStoreFolders ( ) ;
2014-10-02 16:05:44 -04:00
}
2013-12-03 13:21:50 -05:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
2015-03-31 09:43:42 -04:00
return self . _initFolders ( ) ;
2013-12-03 13:21:50 -05:00
2014-12-15 13:33:02 -05:00
} ) . then ( function ( ) {
self . done ( ) ;
} ) . catch ( function ( err ) {
2014-10-02 16:05:44 -04:00
self . done ( ) ; // stop the spinner
2014-12-15 13:33:02 -05:00
throw err ;
} ) ;
2014-10-02 16:05:44 -04:00
} ;
2015-03-31 09:43:42 -04:00
Email . prototype . _initFolders = function ( ) {
2014-10-02 16:05:44 -04:00
var self = this ;
self . _account . folders . forEach ( function ( folder ) {
2015-03-31 09:43:42 -04:00
folder . modseq = folder . modseq || 0 ;
2015-04-09 08:46:33 -04:00
folder . count = folder . count || 0 ;
2015-03-31 09:43:42 -04:00
folder . uids = folder . uids || [ ] ; // attach an empty uids array to the folder
folder . uids . sort ( function ( a , b ) {
return a - b ;
} ) ;
folder . messages = folder . messages || folder . uids . map ( function ( uid ) {
// fill the messages array with dummy messages, messages will be fetched later
return {
uid : uid
} ;
} ) ;
2014-10-02 16:05:44 -04:00
} ) ;
2014-12-15 13:33:02 -05:00
2015-03-31 09:43:42 -04:00
var inbox = _ . findWhere ( self . _account . folders , {
type : FOLDER _TYPE _INBOX
2014-12-16 08:12:30 -05:00
} ) ;
2015-03-31 09:43:42 -04:00
if ( inbox && inbox . messages . length ) {
return self . getBody ( {
folder : inbox ,
messages : inbox . messages . slice ( - 30 )
} ) . catch ( self . _dialog . error ) ;
}
2014-10-02 16:05:44 -04:00
} ;
2014-11-18 12:44:00 -05:00
Email . prototype . busy = function ( ) {
2014-10-02 16:05:44 -04:00
this . _account . busy ++ ;
} ;
2013-10-04 10:29:32 -04:00
2014-11-18 12:44:00 -05:00
Email . prototype . done = function ( ) {
2014-10-02 16:05:44 -04:00
if ( this . _account . busy > 0 ) {
this . _account . busy -- ;
}
} ;
//
//
// IMAP API
//
//
/ * *
* Mark messages as un - / r e a d o r u n - / a n s w e r e d o n I M A P
*
* @ param { Object } options . folder The folder where to find the message
* @ param { Number } options . uid The uid for which to change the flags
* @ param { Number } options . unread Un - / R e a d f l a g
* @ param { Number } options . answered Un - / A n s w e r e d f l a g
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _imapMark = function ( options ) {
var self = this ;
2014-05-23 08:23:50 -04:00
2015-02-12 10:10:46 -05:00
return new Promise ( function ( resolve ) {
self . checkOnline ( ) ;
resolve ( ) ;
} ) . then ( function ( ) {
2014-12-15 13:33:02 -05:00
options . path = options . folder . path ;
2015-02-12 10:10:46 -05:00
return self . _imapClient . updateFlags ( options ) ;
2014-12-15 13:33:02 -05:00
} ) ;
2014-10-02 16:05:44 -04:00
} ;
/ * *
* If we ' re in the trash folder or no trash folder is available , this deletes a message from IMAP .
* Otherwise , it moves a message to the trash folder .
*
* @ param { Object } options . folder The folder where to find the message
* @ param { Number } options . uid The uid of the message
2014-12-15 13:33:02 -05:00
* @ return { Promise }
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _imapDeleteMessage = function ( options ) {
var self = this ;
2014-12-16 08:12:30 -05:00
return new Promise ( function ( resolve ) {
2014-12-15 13:33:02 -05:00
self . checkOnline ( ) ;
2014-12-16 08:12:30 -05:00
resolve ( ) ;
2014-12-15 13:33:02 -05:00
2014-12-16 08:12:30 -05:00
} ) . then ( function ( ) {
2014-12-15 13:33:02 -05:00
var trash = _ . findWhere ( self . _account . folders , {
type : FOLDER _TYPE _TRASH
2014-10-02 16:05:44 -04:00
} ) ;
2014-05-23 08:23:50 -04:00
2014-12-15 13:33:02 -05:00
// there's no known trash folder to move the mail to or we're in the trash folder, so we can purge the message
if ( ! trash || options . folder === trash ) {
2015-02-12 10:10:46 -05:00
return self . _imapClient . deleteMessage ( {
path : options . folder . path ,
uid : options . uid
} ) ;
2014-12-15 13:33:02 -05:00
}
2014-07-11 09:03:03 -04:00
2014-12-16 08:12:30 -05:00
return self . _imapMoveMessage ( {
2014-12-15 13:33:02 -05:00
folder : options . folder ,
destination : trash ,
2014-10-02 16:05:44 -04:00
uid : options . uid
2014-12-16 08:12:30 -05:00
} ) ;
2014-12-15 13:33:02 -05:00
} ) ;
2014-11-04 14:31:09 -05:00
} ;
/ * *
* Move stuff around on the server
*
* @ param { String } options . folder The folder
* @ param { Number } options . destination The destination folder
* @ param { String } options . uid the message ' s uid
2014-12-15 13:33:02 -05:00
* @ return { Promise }
2014-11-04 14:31:09 -05:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _imapMoveMessage = function ( options ) {
var self = this ;
2015-02-12 10:10:46 -05:00
return new Promise ( function ( resolve ) {
2014-12-15 13:33:02 -05:00
self . checkOnline ( ) ;
2015-02-12 10:10:46 -05:00
resolve ( ) ;
} ) . then ( function ( ) {
return self . _imapClient . moveMessage ( {
2014-12-15 13:33:02 -05:00
path : options . folder . path ,
destination : options . destination . path ,
uid : options . uid
} ) ;
} ) ;
2014-10-02 16:05:44 -04:00
} ;
2014-10-22 06:25:27 -04:00
/ * *
* Uploads a built message to a folder
*
* @ param { Object } options . folder The folder where to find the message
* @ param { String } options . message The rfc2822 compatible raw ASCII e - mail source
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _imapUploadMessage = function ( options ) {
var self = this ;
2015-02-12 10:10:46 -05:00
return self . _imapClient . uploadMessage ( {
path : options . folder . path ,
message : options . message
2014-12-15 13:33:02 -05:00
} ) ;
2014-10-22 06:25:27 -04:00
} ;
2015-03-31 09:43:42 -04:00
/ * *
* Fetch messages from imap
* /
Email . prototype . _fetchMessages = function ( options ) {
var self = this ,
messages = options . messages ,
folder = options . folder ;
return new Promise ( function ( resolve ) {
self . checkOnline ( ) ;
resolve ( ) ;
} ) . then ( function ( ) {
// fetch all the metadata at once
return self . _imapClient . listMessages ( {
path : folder . path ,
uids : _ . pluck ( messages , MSG _ATTR _UID )
} ) ;
} ) . then ( function ( msgs ) {
messages = msgs ;
// displays the clip in the UI if the message contains attachments
messages . forEach ( function ( message ) {
message . attachments = message . bodyParts . filter ( function ( bodyPart ) {
return bodyPart . type === MSG _PART _TYPE _ATTACHMENT ;
} ) ;
} ) ;
// get the bodies from imap (individual roundtrips per msg)
var jobs = [ ] ;
messages . forEach ( function ( message ) {
// fetch only the content for non-attachment body parts (encrypted, signed, text, html, resources referenced from the html)
var contentParts = message . bodyParts . filter ( function ( bodyPart ) {
return bodyPart . type !== MSG _PART _TYPE _ATTACHMENT || ( bodyPart . type === MSG _PART _TYPE _ATTACHMENT && bodyPart . id ) ;
} ) ;
var attachmentParts = message . bodyParts . filter ( function ( bodyPart ) {
return bodyPart . type === MSG _PART _TYPE _ATTACHMENT && ! bodyPart . id ;
} ) ;
if ( ! contentParts . length ) {
return ;
}
// do the imap roundtrip
var job = self . _getBodyParts ( {
folder : folder ,
uid : message . uid ,
bodyParts : contentParts
} ) . then ( function ( parsedBodyParts ) {
// concat parsed bodyparts and the empty attachment parts
message . bodyParts = parsedBodyParts . concat ( attachmentParts ) ;
// store fetched message
return self . _localStoreMessages ( {
folder : folder ,
emails : [ message ]
} ) ;
} ) . catch ( function ( err ) {
// ignore errors with err.hide, throw otherwise
if ( err . hide ) {
return ;
} else {
throw err ;
}
} ) ;
jobs . push ( job ) ;
} ) ;
return Promise . all ( jobs ) ;
} ) . then ( function ( ) {
// update the folder's last known modseq if necessary
var highestModseq = Math . max . apply ( null , _ . pluck ( messages , MSG _ATTR _MODSEQ ) . map ( function ( modseq ) {
return parseInt ( modseq , 10 ) ;
} ) ) ;
if ( highestModseq > folder . modseq ) {
folder . modseq = highestModseq ;
return self . _localStoreFolders ( ) ;
}
} ) . then ( function ( ) {
updateUnreadCount ( folder ) ; // update the unread count
return messages ;
} ) ;
} ;
2014-10-02 16:05:44 -04:00
/ * *
* Stream an email messsage ' s body
* @ param { String } options . folder The folder
* @ param { String } options . uid the message ' s uid
2015-03-31 09:43:42 -04:00
* @ param { Object } options . bodyParts The message parts
2014-10-02 16:05:44 -04:00
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _getBodyParts = function ( options ) {
2014-10-02 16:05:44 -04:00
var self = this ;
2015-02-12 10:10:46 -05:00
return new Promise ( function ( resolve ) {
2014-12-15 13:33:02 -05:00
self . checkOnline ( ) ;
2015-02-12 10:10:46 -05:00
resolve ( ) ;
} ) . then ( function ( ) {
2014-12-15 13:33:02 -05:00
options . path = options . folder . path ;
2015-02-12 10:10:46 -05:00
return self . _imapClient . getBodyParts ( options ) ;
} ) . then ( function ( ) {
2015-02-13 11:10:44 -05:00
if ( options . bodyParts . filter ( function ( bodyPart ) {
2015-03-31 09:43:42 -04:00
return ! ( bodyPart . raw || bodyPart . content ) ;
} ) . length ) {
2015-02-13 11:10:44 -05:00
var error = new Error ( 'Can not get the contents of this message. It has already been deleted!' ) ;
error . hide = true ;
throw error ;
}
2015-02-12 10:10:46 -05:00
return self . _parse ( options ) ;
2014-10-02 16:05:44 -04:00
} ) ;
} ;
//
//
// Local Storage API
//
//
2015-03-31 09:43:42 -04:00
/ * *
* persist encrypted list in device storage
* note : the folders in the ui also include the messages array , so let ' s create a clean array here
* /
Email . prototype . _localStoreFolders = function ( ) {
var folders = this . _account . folders . map ( function ( folder ) {
return {
name : folder . name ,
path : folder . path ,
type : folder . type ,
modseq : folder . modseq ,
wellknown : ! ! folder . wellknown ,
uids : folder . uids
} ;
} ) ;
return this . _devicestorage . storeList ( [ folders ] , FOLDER _DB _TYPE ) ;
} ;
2014-10-02 16:05:44 -04:00
/ * *
* List the locally available items form the indexed db stored under "email_[FOLDER PATH]_[MESSAGE UID]" ( if a message was provided ) ,
* or "email_[FOLDER PATH]" , respectively
*
* @ param { Object } options . folder The folder for which to list the content
* @ param { Object } options . uid A specific uid to look up locally in the folder
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _localListMessages = function ( options ) {
2015-03-31 09:43:42 -04:00
var query ;
var needsExactMatch = typeof options . exactmatch === 'undefined' ? true : options . exactmatch ;
if ( Array . isArray ( options . uid ) ) {
// batch list
query = options . uid . map ( function ( uid ) {
return 'email_' + options . folder . path + ( uid ? '_' + uid : '' ) ;
} ) ;
} else {
// single list
query = 'email_' + options . folder . path + ( options . uid ? '_' + options . uid : '' ) ;
}
return this . _devicestorage . listItems ( query , needsExactMatch ) ;
2014-10-02 16:05:44 -04:00
} ;
/ * *
* Stores a bunch of messages to the indexed db . The messages are stored under "email_[FOLDER PATH]_[MESSAGE UID]"
*
* @ param { Object } options . folder The folder for which to list the content
* @ param { Array } options . messages The messages to store
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _localStoreMessages = function ( options ) {
2014-10-02 16:05:44 -04:00
var dbType = 'email_' + options . folder . path ;
2014-12-15 13:33:02 -05:00
return this . _devicestorage . storeList ( options . emails , dbType ) ;
2014-10-02 16:05:44 -04:00
} ;
/ * *
* Stores a bunch of messages to the indexed db . The messages are stored under "email_[FOLDER PATH]_[MESSAGE UID]"
*
* @ param { Object } options . folder The folder for which to list the content
* @ param { Array } options . messages The messages to store
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _localDeleteMessage = function ( options ) {
2014-10-02 16:05:44 -04:00
var path = options . folder . path ,
uid = options . uid ,
id = options . id ;
if ( ! path || ! ( uid || id ) ) {
2014-12-15 13:33:02 -05:00
return new Promise ( function ( ) {
throw new Error ( 'Invalid options!' ) ;
2014-10-02 16:05:44 -04:00
} ) ;
}
2014-06-25 08:13:46 -04:00
2014-10-02 16:05:44 -04:00
var dbType = 'email_' + path + '_' + ( uid || id ) ;
2014-12-15 13:33:02 -05:00
return this . _devicestorage . removeList ( dbType ) ;
2014-10-02 16:05:44 -04:00
} ;
2014-10-22 06:25:27 -04:00
//
//
// Internal Helper Methods
//
//
2014-12-15 13:33:02 -05:00
2015-04-09 08:46:33 -04:00
/ * *
2015-03-31 09:43:42 -04:00
* Helper method that extracts a message body from the body parts
*
* @ param { Object } message DTO
* /
Email . prototype . _extractBody = function ( message ) {
var self = this ;
return new Promise ( function ( resolve ) {
resolve ( ) ;
} ) . then ( function ( ) {
// extract the content
if ( message . encrypted ) {
// show the encrypted message
message . body = filterBodyParts ( message . bodyParts , MSG _PART _TYPE _ENCRYPTED ) [ 0 ] . content ;
return ;
}
var root = message . bodyParts ;
if ( message . signed ) {
// PGP/MIME signed
var signedRoot = filterBodyParts ( message . bodyParts , MSG _PART _TYPE _SIGNED ) [ 0 ] ; // in case of a signed message, you only want to show the signed content and ignore the rest
message . signedMessage = signedRoot . signedMessage ;
message . signature = signedRoot . signature ;
root = signedRoot . content ;
}
var body = _ . pluck ( filterBodyParts ( root , MSG _PART _TYPE _TEXT ) , MSG _PART _ATTR _CONTENT ) . join ( '\n' ) ;
// if the message is plain text and contains pgp/inline, we are only interested in the encrypted content, the rest (corporate mail footer, attachments, etc.) is discarded.
var pgpInlineMatch = /^-{5}BEGIN PGP MESSAGE-{5}[\s\S]*-{5}END PGP MESSAGE-{5}$/im . exec ( body ) ;
if ( pgpInlineMatch ) {
message . body = pgpInlineMatch [ 0 ] ; // show the plain text content
message . encrypted = true ; // signal the ui that we're handling encrypted content
// replace the bodyParts info with an artificial bodyPart of type "encrypted"
message . bodyParts = [ {
type : MSG _PART _TYPE _ENCRYPTED ,
content : pgpInlineMatch [ 0 ] ,
_isPgpInline : true // used internally to avoid trying to parse non-MIME text with the mailreader
} ] ;
return ;
}
// any content before/after the PGP block will be discarded, untrusted attachments and html is ignored
var clearSignedMatch = /^-{5}BEGIN PGP SIGNED MESSAGE-{5}\nHash:[ ][^\n]+\n(?:[A-Za-z]+:[ ][^\n]+\n)*\n([\s\S]*)\n-{5}BEGIN PGP SIGNATURE-{5}[\S\s]*-{5}END PGP SIGNATURE-{5}$/im . exec ( body ) ;
if ( clearSignedMatch ) {
// PGP/INLINE signed
message . signed = true ;
message . clearSignedMessage = clearSignedMatch [ 0 ] ;
2015-05-07 05:28:11 -04:00
body = ( clearSignedMatch [ 1 ] || '' ) . replace ( /^- /gm , '' ) ; // remove dash escaping https://tools.ietf.org/html/rfc4880#section-7.1
2015-03-31 09:43:42 -04:00
}
if ( ! message . signed ) {
// message is not signed, so we're done here
return setBody ( body , root ) ;
}
// check the signatures for signed messages
return self . _checkSignatures ( message ) . then ( function ( signaturesValid ) {
message . signed = typeof signaturesValid !== 'undefined' ;
message . signaturesValid = signaturesValid ;
setBody ( body , root ) ;
} ) ;
} ) ;
function setBody ( body , root ) {
message . body = body ;
if ( ! message . clearSignedMessage ) {
message . attachments = filterBodyParts ( root , MSG _PART _TYPE _ATTACHMENT ) ;
message . html = _ . pluck ( filterBodyParts ( root , MSG _PART _TYPE _HTML ) , MSG _PART _ATTR _CONTENT ) . join ( '\n' ) ;
inlineExternalImages ( message ) ;
}
}
} ;
2014-12-15 13:33:02 -05:00
/ * *
* Parse an email using the mail reader
* @ param { Object } options The option to be passed to the mailreader
* @ return { Promise }
* /
Email . prototype . _parse = function ( options ) {
2014-12-16 08:12:30 -05:00
var self = this ;
2014-12-15 13:33:02 -05:00
return new Promise ( function ( resolve , reject ) {
self . _mailreader . parse ( options , function ( err , root ) {
if ( err ) {
reject ( err ) ;
} else {
resolve ( root ) ;
}
} ) ;
} ) ;
} ;
2014-10-22 06:25:27 -04:00
/ * *
* Uploads a message to the sent folder , if necessary .
* Calls back immediately if ignoreUploadOnSent == true or not sent folder was found .
2014-11-04 14:31:09 -05:00
*
2014-10-22 06:25:27 -04:00
* @ param { String } options . message The rfc2822 compatible raw ASCII e - mail source
* /
2014-12-15 13:33:02 -05:00
Email . prototype . _uploadToSent = function ( options ) {
2014-10-22 06:25:27 -04:00
var self = this ;
2014-12-16 08:12:30 -05:00
self . busy ( ) ;
return new Promise ( function ( resolve ) {
resolve ( ) ;
2014-10-22 06:25:27 -04:00
2014-12-16 08:12:30 -05:00
} ) . then ( function ( ) {
2014-12-15 13:33:02 -05:00
// upload the sent message to the sent folder if necessary
var sentFolder = _ . findWhere ( self . _account . folders , {
type : FOLDER _TYPE _SENT
} ) ;
2014-10-22 06:25:27 -04:00
2014-12-15 13:33:02 -05:00
// return for wrong usage
if ( self . ignoreUploadOnSent || ! sentFolder || ! options . message ) {
return ;
}
2014-10-22 06:25:27 -04:00
2014-12-15 13:33:02 -05:00
// upload
2014-12-16 08:12:30 -05:00
return self . _imapUploadMessage ( {
2014-12-15 13:33:02 -05:00
folder : sentFolder ,
message : options . message
} ) ;
2014-12-16 08:12:30 -05:00
} ) . then ( function ( ) {
self . done ( ) ;
} ) . catch ( function ( err ) {
self . done ( ) ;
throw err ;
2014-10-22 06:25:27 -04:00
} ) ;
} ;
2014-12-15 13:33:02 -05:00
/ * *
* Check if the client is online and throw an error if this is not the case .
* /
Email . prototype . checkOnline = function ( ) {
2014-12-16 08:12:30 -05:00
if ( ! this . _account . online ) {
2014-12-15 13:33:02 -05:00
var err = new Error ( 'Client is currently offline!' ) ;
err . code = 42 ;
throw err ;
}
} ;
2014-10-22 06:25:27 -04:00
2014-11-12 10:41:40 -05:00
//
//
// External Heler Methods
//
//
2014-12-15 13:33:02 -05:00
2014-11-12 10:41:40 -05:00
/ * *
* Checks whether we need to upload to the sent folder after sending an email .
*
* @ param { String } hostname The hostname to check
* @ return { Boolean } true if upload can be ignored , otherwise false
* /
2014-11-18 12:44:00 -05:00
Email . prototype . checkIgnoreUploadOnSent = function ( hostname ) {
2014-11-12 10:41:40 -05:00
for ( var i = 0 ; i < config . ignoreUploadOnSentDomains . length ; i ++ ) {
if ( config . ignoreUploadOnSentDomains [ i ] . test ( hostname ) ) {
return true ;
}
}
return false ;
} ;
2015-01-22 06:07:14 -05:00
/ * *
* Check if the user agent is online .
* /
Email . prototype . isOnline = function ( ) {
return navigator . onLine ;
} ;
2014-10-02 16:05:44 -04:00
//
//
// Helper Functions
//
//
/ * *
* Updates a folder ' s unread count :
2015-04-27 12:15:36 -04:00
* - For the outbox , that ' s the total number of messages ( countAllMessages === true ) ,
* - For every other folder , it ' s the number of unread messages ( countAllMessages === falsy )
2014-10-02 16:05:44 -04:00
* /
2015-04-27 12:15:36 -04:00
function updateUnreadCount ( folder , countAllMessages ) {
folder . count = countAllMessages ? folder . messages . length : _ . filter ( folder . messages , function ( msg ) {
return msg . unread ;
} ) . length ;
2014-10-02 16:05:44 -04:00
}
/ * *
* Helper function that recursively traverses the body parts tree . Looks for bodyParts that match the provided type and aggregates them
*
* @ param { Array } bodyParts The bodyParts array
* @ param { String } type The type to look up
* @ param { undefined } result Leave undefined , only used for recursion
* /
function filterBodyParts ( bodyParts , type , result ) {
result = result || [ ] ;
bodyParts . forEach ( function ( part ) {
if ( part . type === type ) {
result . push ( part ) ;
} else if ( Array . isArray ( part . content ) ) {
filterBodyParts ( part . content , type , result ) ;
}
} ) ;
return result ;
}
/ * *
* Helper function that looks through the HTML content for < img src = "cid:..." > and
* inlines the images linked internally . Manipulates message . html as a side - effect .
* If no attachment matching the internal reference is found , or constructing a data
* uri fails , just remove the source .
*
* @ param { Object } message DTO
* /
function inlineExternalImages ( message ) {
message . html = message . html . replace ( /(<img[^>]+\bsrc=['"])cid:([^'">]+)(['"])/ig , function ( match , prefix , src , suffix ) {
var localSource = '' ,
payload = '' ;
var internalReference = _ . findWhere ( message . attachments , {
id : src
} ) ;
2014-06-25 08:13:46 -04:00
2014-10-02 16:05:44 -04:00
if ( internalReference ) {
for ( var i = 0 ; i < internalReference . content . byteLength ; i ++ ) {
payload += String . fromCharCode ( internalReference . content [ i ] ) ;
2014-06-25 08:13:46 -04:00
}
2014-06-25 10:05:14 -04:00
2014-10-02 16:05:44 -04:00
try {
localSource = 'data:application/octet-stream;base64,' + btoa ( payload ) ; // try to replace the source
} catch ( e ) { }
}
return prefix + localSource + suffix ;
} ) ;
2014-11-18 12:44:00 -05:00
}